mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-15 09:23:08 +08:00
🐛 fix(query-editor): 修复 SQL Server 多查询冗余影响行数结果
- 将 SQL Server 成对返回的数据结果与 affectedRows 归属同一语句 - 保留 DML 影响行数与存储过程真实多结果集 - 补充后端正反用例及 V2 结果标签回归测试
This commit is contained in:
@@ -1085,6 +1085,53 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(messageApi.success).toHaveBeenCalledWith('已执行完成,生成 1 个结果集。');
|
||||
});
|
||||
|
||||
it('hides redundant sqlserver affected-row status results for every statement in a batch', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
storeState.connections[0].config.type = 'sqlserver';
|
||||
storeState.connections[0].config.database = 'master';
|
||||
backendApp.DBQueryMulti.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: [
|
||||
{ statementIndex: 1, columns: ['value'], rows: [{ value: 1 }] },
|
||||
{ statementIndex: 1, columns: ['affectedRows'], rows: [{ affectedRows: 1 }] },
|
||||
{ statementIndex: 2, columns: ['value'], rows: [{ value: 2 }] },
|
||||
{ statementIndex: 2, columns: ['affectedRows'], rows: [{ affectedRows: 1 }] },
|
||||
],
|
||||
});
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<QueryEditor tab={createTab({ dbName: 'master', query: 'SELECT 1;\nSELECT 2;' })} />);
|
||||
});
|
||||
|
||||
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).toContain('结果 2');
|
||||
expect(rendered).not.toContain('结果 3');
|
||||
expect(rendered).not.toContain('结果 4');
|
||||
expect(rendered).not.toContain('影响行数:1');
|
||||
expect(messageApi.success).toHaveBeenCalledWith('已执行完成,生成 2 个结果集。');
|
||||
|
||||
const resultTabButtons = renderer!.root.findAll((node) =>
|
||||
node.type === 'button' && String(node.props['data-tab-key'] || '').startsWith('result-'));
|
||||
expect(resultTabButtons).toHaveLength(2);
|
||||
|
||||
await act(async () => {
|
||||
resultTabButtons[1].props.onClick();
|
||||
});
|
||||
|
||||
expect(dataGridState.latestProps?.columnNames).toEqual(['value']);
|
||||
expect(dataGridState.latestProps?.data?.[0]).toMatchObject({ value: 2 });
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
@@ -1443,6 +1443,21 @@ func normalizeNativeResultStatementIndexes(dbType string, statements []string, r
|
||||
for idx := range results {
|
||||
results[idx].StatementIndex = idx + 1
|
||||
}
|
||||
case len(results) == len(statements)*2:
|
||||
// go-mssqldb 会在每个 SELECT 数据结果后再发送一条 MsgRowsAffected。
|
||||
// 仅在整个批次严格呈现 [数据结果, affectedRows] 成对结构时补索引,
|
||||
// 避免把存储过程返回的多个真实结果集错误归并到不同语句。
|
||||
for statementIdx := range statements {
|
||||
resultIdx := statementIdx * 2
|
||||
if isAffectedRowsResultSet(results[resultIdx]) || !isAffectedRowsResultSet(results[resultIdx+1]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
for statementIdx := range statements {
|
||||
resultIdx := statementIdx * 2
|
||||
results[resultIdx].StatementIndex = statementIdx + 1
|
||||
results[resultIdx+1].StatementIndex = statementIdx + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2258,6 +2258,81 @@ func TestDBQueryMultiNormalizesSingleSQLServerSelectAffectedRowsStatementIndex(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiNormalizesSQLServerSelectAffectedRowsPairsByStatement(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
newDatabaseFunc = originalNewDatabaseFunc
|
||||
})
|
||||
|
||||
query := "SELECT 1;\nSELECT 2;"
|
||||
baseDB := &fakeBatchWriteDB{
|
||||
multiResult: map[string][]connection.ResultSetData{
|
||||
query: {
|
||||
{
|
||||
Rows: []map[string]interface{}{{"value": int64(1)}},
|
||||
Columns: []string{"value"},
|
||||
},
|
||||
{
|
||||
Rows: []map[string]interface{}{{"affectedRows": int64(1)}},
|
||||
Columns: []string{"affectedRows"},
|
||||
},
|
||||
{
|
||||
Rows: []map[string]interface{}{{"value": int64(2)}},
|
||||
Columns: []string{"value"},
|
||||
},
|
||||
{
|
||||
Rows: []map[string]interface{}{{"affectedRows": int64(1)}},
|
||||
Columns: []string{"affectedRows"},
|
||||
},
|
||||
},
|
||||
},
|
||||
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: "sqlserver", Host: "127.0.0.1", Port: 1433, User: "sa"}
|
||||
|
||||
result := app.DBQueryMulti(config, "master", query, "sqlserver-select-pairs-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) != 4 {
|
||||
t.Fatalf("expected four raw SQL Server result sets, got %#v", resultSets)
|
||||
}
|
||||
wantStatementIndexes := []int{1, 1, 2, 2}
|
||||
for idx, want := range wantStatementIndexes {
|
||||
if got := resultSets[idx].StatementIndex; got != want {
|
||||
t.Fatalf("result set %d statementIndex = %d, want %d; all results: %#v", idx, got, want, resultSets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeNativeResultStatementIndexesKeepsAmbiguousSQLServerResultsUnassigned(t *testing.T) {
|
||||
statements := []string{"SELECT 1", "SELECT 2"}
|
||||
results := []connection.ResultSetData{
|
||||
{Rows: []map[string]interface{}{{"first": int64(1)}}, Columns: []string{"first"}},
|
||||
{Rows: []map[string]interface{}{{"second": int64(2)}}, Columns: []string{"second"}},
|
||||
{Rows: []map[string]interface{}{{"affectedRows": int64(1)}}, Columns: []string{"affectedRows"}},
|
||||
{Rows: []map[string]interface{}{{"affectedRows": int64(1)}}, Columns: []string{"affectedRows"}},
|
||||
}
|
||||
|
||||
normalizeNativeResultStatementIndexes("sqlserver", statements, results)
|
||||
|
||||
for idx, result := range results {
|
||||
if result.StatementIndex != 0 {
|
||||
t.Fatalf("ambiguous result set %d received guessed statementIndex=%d: %#v", idx, result.StatementIndex, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiTreatsBareSQLServerProcedureCallAsQueryFirst(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
|
||||
Reference in New Issue
Block a user