mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-23 23:13:50 +08:00
🐛 fix(sqlserver): 修复普通查询结果被原生多结果集吃空
- 对只读 SQL 的原生多结果集空返回增加顺序回退兜底 - 避免 optional driver-agent 成功返回空结果时前端只剩日志无结果集 - 补充 SQLServer 读查询空结果回退回归测试
This commit is contained in:
@@ -1000,6 +1000,13 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
|
||||
return connection.QueryResult{Success: false, Message: err.Error(), QueryID: queryID}
|
||||
}
|
||||
|
||||
// 某些 optional driver-agent 的原生多结果集路径会异常返回“成功但无任何结果集”。
|
||||
// 对只读查询这是不可信信号,回退到逐条执行可以避免普通 SELECT 在结果面板中被吃空。
|
||||
if useNativeMultiResult && allReadOnly && results != nil && len(results) == 0 && len(resultMessages) == 0 {
|
||||
logger.Warnf("DBQueryMulti 原生多结果集返回空结果,将回退逐条执行:%s SQL片段=%q", formatConnSummary(runConfig), sqlSnippet(query))
|
||||
results = nil
|
||||
}
|
||||
|
||||
// 驱动支持多结果集,直接返回
|
||||
if results != nil {
|
||||
return connection.QueryResult{Success: true, Data: results, Messages: resultMessages, QueryID: queryID}
|
||||
|
||||
@@ -34,6 +34,11 @@ type fakeNativeMultiResultDB struct {
|
||||
multiCalls int
|
||||
}
|
||||
|
||||
type fakeEmptyNativeMultiResultDB struct {
|
||||
*fakeBatchWriteDB
|
||||
multiCalls int
|
||||
}
|
||||
|
||||
func (f *fakeNativeMultiResultDB) QueryMulti(query string) ([]connection.ResultSetData, error) {
|
||||
results, _, err := f.QueryMultiWithMessages(query)
|
||||
return results, err
|
||||
@@ -67,6 +72,28 @@ func (f *fakeNativeMultiResultDB) QueryMultiContextWithMessages(ctx context.Cont
|
||||
}}, append([]string(nil), messages...), nil
|
||||
}
|
||||
|
||||
func (f *fakeEmptyNativeMultiResultDB) QueryMulti(query string) ([]connection.ResultSetData, error) {
|
||||
results, _, err := f.QueryMultiWithMessages(query)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (f *fakeEmptyNativeMultiResultDB) QueryMultiWithMessages(query string) ([]connection.ResultSetData, []string, error) {
|
||||
return f.QueryMultiContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (f *fakeEmptyNativeMultiResultDB) QueryMultiContext(ctx context.Context, query string) ([]connection.ResultSetData, error) {
|
||||
results, _, err := f.QueryMultiContextWithMessages(ctx, query)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (f *fakeEmptyNativeMultiResultDB) QueryMultiContextWithMessages(ctx context.Context, query string) ([]connection.ResultSetData, []string, error) {
|
||||
f.multiCalls++
|
||||
if err := f.queryErr[query]; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return []connection.ResultSetData{}, nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeBatchWriteDB) Connect(config connection.ConnectionConfig) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1332,6 +1359,57 @@ func TestDBQueryMultiRunsSQLServerStatisticsBatchNatively(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiFallsBackWhenNativeReadOnlyBatchReturnsEmptyResults(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
newDatabaseFunc = originalNewDatabaseFunc
|
||||
})
|
||||
|
||||
query := "SELECT 1 AS value"
|
||||
baseDB := &fakeBatchWriteDB{
|
||||
queryMap: map[string][]map[string]interface{}{
|
||||
query: {
|
||||
{"value": 1},
|
||||
},
|
||||
},
|
||||
fieldMap: map[string][]string{
|
||||
query: {"value"},
|
||||
},
|
||||
queryErr: map[string]error{},
|
||||
}
|
||||
fakeDB := &fakeEmptyNativeMultiResultDB{fakeBatchWriteDB: baseDB}
|
||||
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, "master", query, "sqlserver-empty-native-read-fallback-test")
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
|
||||
}
|
||||
if fakeDB.multiCalls != 1 {
|
||||
t.Fatalf("expected one native multi-result attempt, got %d", fakeDB.multiCalls)
|
||||
}
|
||||
if baseDB.session == nil {
|
||||
t.Fatal("expected empty native result to fall back to pinned session query")
|
||||
}
|
||||
if baseDB.session.queryCalls != 1 {
|
||||
t.Fatalf("expected fallback to query through pinned session once, got %d", baseDB.session.queryCalls)
|
||||
}
|
||||
resultSets, ok := result.Data.([]connection.ResultSetData)
|
||||
if !ok {
|
||||
t.Fatalf("expected []connection.ResultSetData, got %T", result.Data)
|
||||
}
|
||||
if len(resultSets) != 1 {
|
||||
t.Fatalf("expected one fallback result set, got %#v", resultSets)
|
||||
}
|
||||
if got := resultSets[0].Rows[0]["value"]; got != 1 {
|
||||
t.Fatalf("expected fallback SELECT result value=1, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiUsesPinnedSessionForSequentialFallback(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
|
||||
Reference in New Issue
Block a user