🐛 fix(query-results): 修复多结果集回退空结果页

This commit is contained in:
Syngnat
2026-06-29 11:09:40 +08:00
parent f55a332ead
commit 8e857b9aee
3 changed files with 131 additions and 11 deletions

View File

@@ -1105,6 +1105,50 @@ describe('QueryEditor external SQL save', () => {
expect(textContent(renderer!.toJSON())).not.toContain('影响行数0');
});
it('shows the data result tab in V2 when the SQL log tab is already visible', async () => {
storeState.appearance.uiVersion = 'v2';
storeState.sqlLogs = [{
id: 'log-existing',
timestamp: Date.now(),
sql: 'SELECT * FROM ldf_server.mes_work_order',
status: 'success',
duration: 120,
}];
storeState.connections[0].config.type = 'kingbase';
storeState.connections[0].config.database = 'ldf_server_dbs_dev';
backendApp.DBQueryMulti.mockResolvedValueOnce({
success: true,
data: [{
statementIndex: 1,
columns: ['work_order'],
rows: [{ work_order: 'MO-20260629' }],
}],
});
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({
dbName: 'ldf_server_dbs_dev',
query: 'SELECT * FROM ldf_server.mes_work_order;',
resultPanelVisible: true,
})} />);
});
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('日志');
expect(rendered).toContain('结果 1');
expect(dataGridState.latestProps?.columnNames).toEqual(['work_order']);
expect(dataGridState.latestProps?.data?.[0]).toMatchObject({ work_order: 'MO-20260629' });
});
it('prefers sqlserver print output messages over affected-row status results', async () => {
storeState.connections[0].config.type = 'sqlserver';
storeState.connections[0].config.database = 'hydee';

View File

@@ -1232,6 +1232,22 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
statementResults []connection.ResultSetData
usedMultiResult bool
)
runStatementQuery := func() error {
if sessionQueryMessageTarget != nil {
data, columns, messages, err = sessionQueryMessageTarget.QueryContextWithMessages(ctx, stmt)
} else if sessionQueryTarget != nil {
data, columns, err = sessionQueryTarget.QueryContext(ctx, stmt)
} else if q, ok := dbInst.(db.QueryMessageExecer); ok {
data, columns, messages, err = q.QueryContextWithMessages(ctx, stmt)
} else if q, ok := dbInst.(interface {
QueryContext(context.Context, string) ([]map[string]interface{}, []string, error)
}); ok {
data, columns, err = q.QueryContext(ctx, stmt)
} else {
data, columns, err = dbInst.Query(stmt)
}
return err
}
if sessionMultiQueryMessageTarget != nil {
statementResults, messages, err = sessionMultiQueryMessageTarget.QueryMultiContextWithMessages(ctx, stmt)
usedMultiResult = true
@@ -1247,18 +1263,17 @@ func (a *App) DBQueryMulti(config connection.ConnectionConfig, dbName string, qu
} else if q, ok := dbInst.(db.MultiResultQuerier); ok {
statementResults, err = q.QueryMulti(stmt)
usedMultiResult = true
} else if sessionQueryMessageTarget != nil {
data, columns, messages, err = sessionQueryMessageTarget.QueryContextWithMessages(ctx, stmt)
} else if sessionQueryTarget != nil {
data, columns, err = sessionQueryTarget.QueryContext(ctx, stmt)
} else if q, ok := dbInst.(db.QueryMessageExecer); ok {
data, columns, messages, err = q.QueryContextWithMessages(ctx, stmt)
} else if q, ok := dbInst.(interface {
QueryContext(context.Context, string) ([]map[string]interface{}, []string, error)
}); ok {
data, columns, err = q.QueryContext(ctx, stmt)
} else {
data, columns, err = dbInst.Query(stmt)
err = runStatementQuery()
}
if err == nil && usedMultiResult && nativeReadOnlyResultsMissingTabularPayload(isReadStmt, statementResults) {
logger.Warnf("DBQueryMulti 逐条多结果集返回空结果,将回退普通查询(第 %d/%d 条):%s SQL片段=%q", idx+1, len(statements), formatConnSummary(runConfig), sqlSnippet(stmt))
usedMultiResult = false
statementResults = nil
data = nil
columns = nil
messages = nil
err = runStatementQuery()
}
if err == nil {
if usedMultiResult {

View File

@@ -1756,6 +1756,67 @@ func TestDBQueryMultiFallsBackWhenNativeReadOnlyBatchReturnsBlankResultSet(t *te
}
}
func TestDBQueryMultiFallsBackToPlainQueryWhenSequentialMultiStillReturnsBlankResultSet(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
})
query := "SELECT * FROM ldf_server.mes_work_order"
blankNativeResult := []connection.ResultSetData{{
Rows: []map[string]interface{}{},
Columns: []string{},
}}
baseDB := &fakeBatchWriteDB{
queryMap: map[string][]map[string]interface{}{
query: {
{"work_order": "MO-20260629"},
},
},
fieldMap: map[string][]string{
query: {"work_order"},
},
multiResult: map[string][]connection.ResultSetData{
query: blankNativeResult,
},
queryErr: map[string]error{},
}
fakeDB := &fakeNativeMultiResultDB{fakeBatchWriteDB: baseDB}
newDatabaseFunc = func(dbType string) (db.Database, error) {
return fakeDB, nil
}
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
config := connection.ConnectionConfig{Type: "kingbase", Host: "127.0.0.1", Port: 54321, User: "system"}
result := app.DBQueryMulti(config, "ldf_server_dbs_dev", query, "sequential-blank-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 top-level native multi-result attempt, got %d", fakeDB.multiCalls)
}
if baseDB.session == nil {
t.Fatal("expected DBQueryMulti to open a pinned session for sequential fallback")
}
if baseDB.session.queryCalls != 2 {
t.Fatalf("expected sequential multi-result attempt plus plain query fallback, got %d calls", 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 plain query fallback result set, got %#v", resultSets)
}
if !reflect.DeepEqual(resultSets[0].Columns, []string{"work_order"}) {
t.Fatalf("expected fallback columns, got %#v", resultSets[0].Columns)
}
if got := resultSets[0].Rows[0]["work_order"]; got != "MO-20260629" {
t.Fatalf("expected fallback SELECT result work_order=MO-20260629, got %#v", got)
}
}
func TestDBQueryMultiUsesPinnedSessionForSequentialFallback(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {