🐛 fix(query-editor): 补齐SQL Server冗余结果集过滤

- 为 SQL Server 原生多结果集缺失 statementIndex 的场景补齐结果归属
- 修复单条 SELECT 查询尾随 affectedRows 结果被误渲染为额外结果页签的问题
- 补充前后端回归测试并保持存储过程多结果集展示不变
This commit is contained in:
Syngnat
2026-06-24 11:57:19 +08:00
parent 90d84da849
commit d09b968cc0
4 changed files with 88 additions and 4 deletions

View File

@@ -850,11 +850,10 @@ describe('QueryEditor external SQL save', () => {
success: true,
data: [
{
statementIndex: 1,
columns: ['dddwno', 'dddwlist'],
rows: [{ dddwno: '001', dddwlist: 'demo' }],
},
{ statementIndex: 1, columns: ['affectedRows'], rows: [{ affectedRows: 846 }] },
{ columns: ['affectedRows'], rows: [{ affectedRows: 846 }] },
],
});

View File

@@ -3479,10 +3479,20 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const maxRows = Number(queryOptions?.maxRows) || 0;
let anyTruncated = false;
const statementResultCounts = new Map<number, number>();
const resolveSourceStatementIndex = (rsData: any, idx: number): number => {
const explicitStatementIndex = Number(rsData?.statementIndex || 0);
if (explicitStatementIndex > 0) {
return explicitStatementIndex;
}
if (normalizedDbType === 'sqlserver' && sourceStatements.length === 1) {
return 1;
}
return idx + 1;
};
const sqlServerStatementsWithConcreteResults = new Set<number>();
if (normalizedDbType === 'sqlserver') {
resultSetDataArray.forEach((rsData, idx) => {
const sourceStatementIndex = Number(rsData?.statementIndex || idx + 1);
const sourceStatementIndex = resolveSourceStatementIndex(rsData, idx);
const resultMessages = normalizeQueryResultMessages(rsData?.messages);
if (hasConcreteQueryResultSetData(rsData, resultMessages)) {
sqlServerStatementsWithConcreteResults.add(sourceStatementIndex);
@@ -3495,7 +3505,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
for (let idx = 0; idx < resultSetDataArray.length; idx++) {
const rsData = resultSetDataArray[idx];
const sourceStatementIndex = Number(rsData?.statementIndex || idx + 1);
const sourceStatementIndex = resolveSourceStatementIndex(rsData, idx);
const plan = executablePlans[Math.max(0, sourceStatementIndex - 1)];
const originalSql = plan?.originalSql || '';
const executedSql = plan?.executedSql || originalSql;

View File

@@ -1038,6 +1038,9 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
logger.Warnf("DBQueryMulti 原生多结果集返回空结果,将回退逐条执行:%s SQL片段=%q", formatConnSummary(runConfig), sqlSnippet(query))
results = nil
}
if useNativeMultiResult && results != nil {
normalizeNativeResultStatementIndexes(runConfig.Type, statements, results)
}
// 驱动支持多结果集,直接返回
if results != nil {
@@ -1276,6 +1279,33 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
return connection.QueryResult{Success: true, Data: resultSets, QueryID: queryID, Message: fallbackMsg}
}
func normalizeNativeResultStatementIndexes(dbType string, statements []string, results []connection.ResultSetData) {
if !isSQLServerDBType(dbType) || len(results) == 0 {
return
}
hasExplicitStatementIndex := false
for _, result := range results {
if result.StatementIndex > 0 {
hasExplicitStatementIndex = true
break
}
}
if hasExplicitStatementIndex {
return
}
switch {
case len(statements) <= 1:
for idx := range results {
results[idx].StatementIndex = 1
}
case len(results) == len(statements):
for idx := range results {
results[idx].StatementIndex = idx + 1
}
}
}
func shouldUseNativeMultiResultBatch(dbType string, statements []string, allReadOnly bool) bool {
if allReadOnly {
return true

View File

@@ -1525,6 +1525,51 @@ func TestDBQueryMultiKeepsAllResultSetsFromSingleSQLServerStatement(t *testing.T
}
}
func TestDBQueryMultiNormalizesSingleSQLServerSelectAffectedRowsStatementIndex(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
})
query := "select * from c_dddw"
fakeDB := &fakeBatchWriteDB{
multiResult: map[string][]connection.ResultSetData{
query: {
{
Rows: []map[string]interface{}{{"dddwno": "001", "dddwlist": "demo"}},
Columns: []string{"dddwno", "dddwlist"},
},
{
Rows: []map[string]interface{}{{"affectedRows": int64(846)}},
Columns: []string{"affectedRows"},
},
},
},
queryErr: map[string]error{},
}
newDatabaseFunc = func(dbType string) (db.Database, error) {
return fakeDB, nil
}
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
config := connection.ConnectionConfig{Type: "sqlserver", Host: "127.0.0.1", Port: 1433, User: "sa"}
result := app.DBQueryMulti(config, "hydee", query, "sqlserver-select-affectedrows-index-test")
if !result.Success {
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
}
resultSets, ok := result.Data.([]connection.ResultSetData)
if !ok {
t.Fatalf("expected []connection.ResultSetData, got %T", result.Data)
}
if len(resultSets) != 2 {
t.Fatalf("expected two result sets, got %#v", resultSets)
}
if resultSets[0].StatementIndex != 1 || resultSets[1].StatementIndex != 1 {
t.Fatalf("expected select result and trailing affectedRows result to share statementIndex=1, got %#v", resultSets)
}
}
func TestDBQueryMultiTreatsBareSQLServerProcedureCallAsQueryFirst(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {