diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index f47e6ac..19bf808 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -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 }] }, ], }); diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 84a66e6..35ecc39 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -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(); + 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(); 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; diff --git a/internal/app/methods_db.go b/internal/app/methods_db.go index 0862048..bbc0339 100644 --- a/internal/app/methods_db.go +++ b/internal/app/methods_db.go @@ -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 diff --git a/internal/app/methods_db_multi_test.go b/internal/app/methods_db_multi_test.go index 0b4a382..9037c36 100644 --- a/internal/app/methods_db_multi_test.go +++ b/internal/app/methods_db_multi_test.go @@ -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() {