mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-09 06:23:29 +08:00
🐛 fix(db): 修复 OceanBase Oracle 空结果并收敛提示
- OceanBase Oracle 只读查询优先走普通 Query 路径 - 补齐普通执行与托管事务两条回归测试 - 调整多语句回退提示避免误导为协议不支持 - 同步前端注释与多语言文案
This commit is contained in:
@@ -6361,7 +6361,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
if (plan.warning) message.warning(plan.warning);
|
||||
});
|
||||
|
||||
// 后端附带的提示信息(如数据源不支持原生多语句执行的回退提示)
|
||||
// 后端附带的提示信息(如本次改走逐条执行的多语句回退提示)
|
||||
if (res.message) {
|
||||
message.info(res.message);
|
||||
}
|
||||
|
||||
@@ -1420,6 +1420,7 @@ func shouldUseNativeMultiResultBatch(dbType string, statements []string, allRead
|
||||
func shouldPreferPlainReadQueryResult(dbType string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
case "postgres", "postgresql",
|
||||
"oracle",
|
||||
"kingbase", "kingbase8", "kingbasees", "kingbasev8",
|
||||
"highgo", "vastbase",
|
||||
"opengauss", "open_gauss", "open-gauss",
|
||||
|
||||
@@ -529,7 +529,7 @@ func TestMethodsDBQueryMultiMessagesUseLocalizedText(t *testing.T) {
|
||||
rawMessages := []string{
|
||||
`fmt.Sprintf("第 %d 条语句执行失败: %v", idx+1, err)`,
|
||||
`fmt.Sprintf("(前 %d 条已执行成功)", len(resultSets))`,
|
||||
`fmt.Sprintf("当前数据源(%s)不支持原生多语句执行,已自动拆分为 %d 条语句逐条执行。", runConfig.Type, len(statements))`,
|
||||
`fmt.Sprintf("当前数据源(%s)本次未走原生多语句结果路径,已自动拆分为 %d 条语句逐条执行。", runConfig.Type, len(statements))`,
|
||||
}
|
||||
keys := []string{
|
||||
"db.backend.error.multi_statement_execution_failed",
|
||||
@@ -629,7 +629,7 @@ func TestMethodsDBQueryMultiSequentialFallbackUsesEnglishMessages(t *testing.T)
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %+v", result)
|
||||
}
|
||||
if result.Message != "The current data source (mysql) does not support native multi-statement execution. It was automatically split into 2 statements and executed sequentially." {
|
||||
if result.Message != "The current data source (mysql) did not use the native multi-statement result path for this execution. It was automatically split into 2 statements and executed sequentially." {
|
||||
t.Fatalf("expected localized sequential fallback message, got %q", result.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1616,7 +1616,7 @@ func TestDBQueryMultiRunsSQLServerStatisticsBatchNatively(t *testing.T) {
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
|
||||
}
|
||||
if strings.Contains(result.Message, "不支持原生多语句执行") {
|
||||
if strings.Contains(result.Message, "逐条执行") {
|
||||
t.Fatalf("expected SQL Server statistics batch to avoid sequential fallback warning, got %q", result.Message)
|
||||
}
|
||||
if fakeDB.multiCalls != 1 {
|
||||
@@ -1939,6 +1939,73 @@ func TestDBQueryMultiPrefersPlainQueryForDamengReadResults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiPrefersPlainQueryForOceanBaseOracleReadResults(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
newDatabaseFunc = originalNewDatabaseFunc
|
||||
})
|
||||
|
||||
query := "SELECT * FROM EINP_BASICINFO.AC01"
|
||||
nativeEmptyRowsResult := []connection.ResultSetData{{
|
||||
Rows: []map[string]interface{}{},
|
||||
Columns: []string{"AAC001", "AAC003"},
|
||||
}}
|
||||
baseDB := &fakeBatchWriteDB{
|
||||
queryMap: map[string][]map[string]interface{}{
|
||||
query: {
|
||||
{"AAC001": 1001, "AAC003": "张三"},
|
||||
},
|
||||
},
|
||||
fieldMap: map[string][]string{
|
||||
query: {"AAC001", "AAC003"},
|
||||
},
|
||||
multiResult: map[string][]connection.ResultSetData{
|
||||
query: nativeEmptyRowsResult,
|
||||
},
|
||||
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: "oceanbase",
|
||||
Host: "127.0.0.1",
|
||||
Port: 2881,
|
||||
User: "SBDEVREAD",
|
||||
OceanBaseProtocol: "oracle",
|
||||
}
|
||||
|
||||
result := app.DBQueryMulti(config, "SBDEV", query, "oceanbase-oracle-plain-query-result-test")
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
|
||||
}
|
||||
if fakeDB.multiCalls != 0 {
|
||||
t.Fatalf("expected OceanBase Oracle read query to skip top-level native multi-result path, got %d calls", fakeDB.multiCalls)
|
||||
}
|
||||
if baseDB.session == nil {
|
||||
t.Fatal("expected DBQueryMulti to open a pinned session for OceanBase Oracle read query")
|
||||
}
|
||||
if baseDB.session.queryCalls != 1 {
|
||||
t.Fatalf("expected OceanBase Oracle read query to use plain session query once, 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 result set, got %#v", resultSets)
|
||||
}
|
||||
if !reflect.DeepEqual(resultSets[0].Columns, []string{"AAC001", "AAC003"}) {
|
||||
t.Fatalf("expected plain query columns, got %#v", resultSets[0].Columns)
|
||||
}
|
||||
if got := resultSets[0].Rows[0]["AAC003"]; got != "张三" {
|
||||
t.Fatalf("expected plain query SELECT result AAC003=张三, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiUsesPinnedSessionForSequentialFallback(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
@@ -2371,3 +2438,48 @@ func TestExecuteManagedSQLTransactionStatementsPrefersPlainQueryForDamengReadRes
|
||||
t.Fatalf("expected plain query SELECT result NAME=timer_a, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManagedSQLTransactionStatementsPrefersPlainQueryForOceanBaseOracleReadResults(t *testing.T) {
|
||||
query := "SELECT * FROM EINP_BASICINFO.AC01"
|
||||
baseDB := &fakeBatchWriteDB{
|
||||
queryMap: map[string][]map[string]interface{}{
|
||||
query: {
|
||||
{"AAC001": 1001, "AAC003": "张三"},
|
||||
},
|
||||
},
|
||||
fieldMap: map[string][]string{
|
||||
query: {"AAC001", "AAC003"},
|
||||
},
|
||||
multiResult: map[string][]connection.ResultSetData{
|
||||
query: {{
|
||||
Rows: []map[string]interface{}{},
|
||||
Columns: []string{"AAC001", "AAC003"},
|
||||
}},
|
||||
},
|
||||
queryErr: map[string]error{},
|
||||
}
|
||||
session := &fakeBatchWriteSession{parent: baseDB}
|
||||
|
||||
results, err := executeManagedSQLTransactionStatements(
|
||||
context.Background(),
|
||||
session,
|
||||
connection.ConnectionConfig{Type: "oceanbase", OceanBaseProtocol: "oracle"},
|
||||
[]string{query},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("expected executeManagedSQLTransactionStatements success, got %v", err)
|
||||
}
|
||||
if session.queryCalls != 1 {
|
||||
t.Fatalf("expected OceanBase Oracle managed read query to use plain query once, got %d calls", session.queryCalls)
|
||||
}
|
||||
if len(results) != 1 {
|
||||
t.Fatalf("expected one result set, got %#v", results)
|
||||
}
|
||||
if !reflect.DeepEqual(results[0].Columns, []string{"AAC001", "AAC003"}) {
|
||||
t.Fatalf("expected plain query columns, got %#v", results[0].Columns)
|
||||
}
|
||||
if got := results[0].Rows[0]["AAC003"]; got != "张三" {
|
||||
t.Fatalf("expected plain query SELECT result AAC003=张三, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "{{count}} Mitglieder gefunden",
|
||||
"db.backend.message.mongo_primary_credentials_label": "Primär-Anmeldedaten",
|
||||
"db.backend.message.mongo_replica_credentials_label": "Replikat-Anmeldedaten",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "Die aktuelle Datenquelle ({{dbType}}) unterstützt keine native Ausführung mehrerer Anweisungen. Sie wurde automatisch in {{count}} Anweisungen aufgeteilt und nacheinander ausgeführt.",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "Bei dieser Ausführung hat die aktuelle Datenquelle ({{dbType}}) nicht den nativen Mehrfach-Statement-Ergebnispfad verwendet. Sie wurde automatisch in {{count}} Anweisungen aufgeteilt und nacheinander ausgeführt.",
|
||||
"db.backend.message.procedure_dropped": "Gespeicherte Prozedur gelöscht",
|
||||
"db.backend.message.release_success": "Verbindung freigegeben",
|
||||
"db.backend.message.schema_created": "Schema erstellt",
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "Discovered {{count}} members",
|
||||
"db.backend.message.mongo_primary_credentials_label": "primary credentials",
|
||||
"db.backend.message.mongo_replica_credentials_label": "replica credentials",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "The current data source ({{dbType}}) does not support native multi-statement execution. It was automatically split into {{count}} statements and executed sequentially.",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "The current data source ({{dbType}}) did not use the native multi-statement result path for this execution. It was automatically split into {{count}} statements and executed sequentially.",
|
||||
"db.backend.message.procedure_dropped": "Stored procedure dropped",
|
||||
"db.backend.message.release_success": "Connection released",
|
||||
"db.backend.message.schema_created": "Schema created",
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "{{count}} 件のメンバーを検出しました",
|
||||
"db.backend.message.mongo_primary_credentials_label": "プライマリ認証情報",
|
||||
"db.backend.message.mongo_replica_credentials_label": "レプリカ認証情報",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "現在のデータソース({{dbType}})はネイティブな複数ステートメント実行をサポートしていないため、{{count}} 件のステートメントに自動分割して順番に実行しました。",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "現在のデータソース({{dbType}})では今回の実行でネイティブな複数ステートメント結果パスを使用せず、{{count}} 件のステートメントに自動分割して順番に実行しました。",
|
||||
"db.backend.message.procedure_dropped": "ストアドプロシージャを削除しました",
|
||||
"db.backend.message.release_success": "接続を解放しました",
|
||||
"db.backend.message.schema_created": "スキーマを作成しました",
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "Обнаружено участников: {{count}}",
|
||||
"db.backend.message.mongo_primary_credentials_label": "учетные данные primary",
|
||||
"db.backend.message.mongo_replica_credentials_label": "учетные данные реплики",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "Текущий источник данных ({{dbType}}) не поддерживает нативное выполнение нескольких операторов. Запрос был автоматически разделен на {{count}} операторов и выполнен последовательно.",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "Для этого выполнения текущий источник данных ({{dbType}}) не использовал нативный путь результатов для нескольких операторов. Запрос был автоматически разделен на {{count}} операторов и выполнен последовательно.",
|
||||
"db.backend.message.procedure_dropped": "Хранимая процедура удалена",
|
||||
"db.backend.message.release_success": "Подключение освобождено",
|
||||
"db.backend.message.schema_created": "Схема создана",
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "发现 {{count}} 个成员",
|
||||
"db.backend.message.mongo_primary_credentials_label": "主库凭据",
|
||||
"db.backend.message.mongo_replica_credentials_label": "从库凭据",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "当前数据源({{dbType}})不支持原生多语句执行,已自动拆分为 {{count}} 条语句逐条执行。",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "当前数据源({{dbType}})本次未走原生多语句结果路径,已自动拆分为 {{count}} 条语句逐条执行。",
|
||||
"db.backend.message.procedure_dropped": "存储过程删除成功",
|
||||
"db.backend.message.release_success": "连接已释放",
|
||||
"db.backend.message.schema_created": "模式创建成功",
|
||||
|
||||
@@ -4739,7 +4739,7 @@
|
||||
"db.backend.message.mongo_members_discovered": "發現 {{count}} 個成員",
|
||||
"db.backend.message.mongo_primary_credentials_label": "主庫憑證",
|
||||
"db.backend.message.mongo_replica_credentials_label": "從庫憑證",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "目前資料來源({{dbType}})不支援原生多語句執行,已自動拆分為 {{count}} 條語句逐條執行。",
|
||||
"db.backend.message.multi_statement_sequential_fallback": "目前資料來源({{dbType}})本次未走原生多語句結果路徑,已自動拆分為 {{count}} 條語句逐條執行。",
|
||||
"db.backend.message.procedure_dropped": "預存程序刪除成功",
|
||||
"db.backend.message.release_success": "連線已釋放",
|
||||
"db.backend.message.schema_created": "模式建立成功",
|
||||
|
||||
Reference in New Issue
Block a user