🐛 fix(db): 保留多写语句结果并修复 MySQL 字符集参数

- 多条写语句改为逐条返回 affectedRows,避免只显示最后一条结果

- 为写语句结果补充 statementIndex,保持语句与结果映射

- 保留 MySQL charset fallback 逗号,避免驱动解析成 %2C
This commit is contained in:
Syngnat
2026-06-09 14:13:35 +08:00
parent a6105f4807
commit c45961f027
5 changed files with 144 additions and 32 deletions

View File

@@ -10,18 +10,19 @@ import (
)
type fakeBatchWriteDB struct {
batchCalls int
execCalls int
execQueries []string
lastQuery string
lastCtx context.Context
queryCalls int
queryMap map[string][]map[string]interface{}
fieldMap map[string][]string
messageMap map[string][]string
multiResult map[string][]connection.ResultSetData
queryErr map[string]error
session *fakeBatchWriteSession
batchCalls int
execCalls int
execQueries []string
lastQuery string
lastCtx context.Context
queryCalls int
queryMap map[string][]map[string]interface{}
fieldMap map[string][]string
messageMap map[string][]string
multiResult map[string][]connection.ResultSetData
queryErr map[string]error
execAffected map[string]int64
session *fakeBatchWriteSession
}
func (f *fakeBatchWriteDB) Connect(config connection.ConnectionConfig) error {
@@ -52,6 +53,9 @@ func (f *fakeBatchWriteDB) QueryWithMessages(query string) ([]map[string]interfa
func (f *fakeBatchWriteDB) Exec(query string) (int64, error) {
f.execCalls++
f.execQueries = append(f.execQueries, query)
if affected, ok := f.execAffected[query]; ok {
return affected, nil
}
return 1, nil
}
@@ -91,6 +95,9 @@ func (f *fakeBatchWriteDB) ExecContext(ctx context.Context, query string) (int64
f.lastCtx = ctx
f.execCalls++
f.execQueries = append(f.execQueries, query)
if affected, ok := f.execAffected[query]; ok {
return affected, nil
}
return 1, nil
}
@@ -440,13 +447,20 @@ func TestDBQueryWithCancel_DuckDBQueriesDoNotInheritConnectTimeout(t *testing.T)
}
}
func TestDBQueryMultiUsesBatchWriteExecerForAllWriteStatements(t *testing.T) {
func TestDBQueryMultiPreservesPerStatementResultsForMultipleWriteStatements(t *testing.T) {
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
})
fakeDB := &fakeBatchWriteDB{}
firstStmt := "DELETE FROM assets_asset"
secondStmt := "DELETE FROM assets_assetcategory"
fakeDB := &fakeBatchWriteDB{
execAffected: map[string]int64{
firstStmt: 5,
secondStmt: 10,
},
}
newDatabaseFunc = func(dbType string) (db.Database, error) {
return fakeDB, nil
}
@@ -458,31 +472,37 @@ func TestDBQueryMultiUsesBatchWriteExecerForAllWriteStatements(t *testing.T) {
Port: 1433,
User: "sa",
}
query := "INSERT INTO demo(id) VALUES (1);\nINSERT INTO demo(id) VALUES (2);"
query := firstStmt + ";\n" + secondStmt + ";"
result := app.DBQueryMulti(config, "testdb", query, "batch-write-test")
if !result.Success {
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
}
if fakeDB.batchCalls != 1 {
t.Fatalf("expected batch path to run once, got %d", fakeDB.batchCalls)
if fakeDB.batchCalls != 0 {
t.Fatalf("expected multiple write statements to skip batch path so each result can be preserved, got %d", fakeDB.batchCalls)
}
if fakeDB.execCalls != 0 {
t.Fatalf("expected sequential exec path to be skipped, got execCalls=%d", fakeDB.execCalls)
if fakeDB.execCalls != 2 {
t.Fatalf("expected sequential exec path to run twice, got execCalls=%d", fakeDB.execCalls)
}
if fakeDB.lastQuery != query {
t.Fatalf("expected batch query to stay intact, got %q", fakeDB.lastQuery)
if len(fakeDB.execQueries) != 2 || fakeDB.execQueries[0] != firstStmt || fakeDB.execQueries[1] != secondStmt {
t.Fatalf("expected sequential execs to preserve statement order, got %#v", fakeDB.execQueries)
}
resultSets, ok := result.Data.([]connection.ResultSetData)
if !ok {
t.Fatalf("expected []connection.ResultSetData, got %T", result.Data)
}
if len(resultSets) != 1 || len(resultSets[0].Rows) != 1 {
t.Fatalf("expected one affectedRows result set, got %#v", resultSets)
if len(resultSets) != 2 {
t.Fatalf("expected one affectedRows result set per statement, got %#v", resultSets)
}
if got := resultSets[0].Rows[0]["affectedRows"]; got != int64(500) {
t.Fatalf("expected affectedRows=500, got %#v", got)
if len(resultSets[0].Rows) != 1 || len(resultSets[1].Rows) != 1 {
t.Fatalf("expected both result sets to contain a single affectedRows row, got %#v", resultSets)
}
if got := resultSets[0].Rows[0]["affectedRows"]; got != int64(5) {
t.Fatalf("expected first affectedRows=5, got %#v", got)
}
if got := resultSets[1].Rows[0]["affectedRows"]; got != int64(10) {
t.Fatalf("expected second affectedRows=10, got %#v", got)
}
}