From 8481061a03d0f362114bd8b789de79a61448d2ec Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 3 Aug 2026 23:15:47 +0800 Subject: [PATCH] fix(oracle): retry XMLTYPE queries after decode errors Closes #5216 --- agents/drivers/oracle-go/main.go | 35 +++++++++ agents/drivers/oracle-go/main_test.go | 106 ++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index 280676579..0f6529923 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -2960,6 +2960,41 @@ func (s *server) executeQuery(opts queryOptions) (queryResult, error) { } func (s *server) executeSelect(sqlText string, maxRows int, timeoutSecs int) (queryResult, error) { + return executeOracleSelectWithXMLTypeRetry( + sqlText, + func(query string) (queryResult, error) { + return s.executeSelectOnce(query, maxRows, timeoutSecs) + }, + s.rewriteXMLTypeSelectSQL, + ) +} + +func executeOracleSelectWithXMLTypeRetry( + sqlText string, + execute func(string) (queryResult, error), + rewrite func(string) (string, error), +) (queryResult, error) { + result, err := execute(sqlText) + if err == nil || !shouldRetryOracleXMLTypeRewrite(err) { + return result, err + } + rewritten, rewriteErr := rewrite(sqlText) + if rewriteErr != nil || rewritten == sqlText { + return result, err + } + return execute(rewritten) +} + +func shouldRetryOracleXMLTypeRewrite(err error) bool { + if err == nil { + return false + } + message := err.Error() + return strings.Contains(message, "abnormal data representation for date") || + strings.Contains(message, "TTC error: received code ") +} + +func (s *server) executeSelectOnce(sqlText string, maxRows int, timeoutSecs int) (queryResult, error) { rows, err := s.queryRowsWithXMLTypeRewriteIfNeeded(sqlText, timeoutSecs) if err != nil { return queryResult{}, err diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index 9c010c3c3..4babd4e82 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -274,6 +274,112 @@ func TestNormalizeValueKeepsOracleZonedDateTimeOffsets(t *testing.T) { } } +func TestExecuteOracleSelectRetriesXMLTypeDecodeFailures(t *testing.T) { + originalSQL := `SELECT * FROM (SELECT * FROM "DBX"."TEST_LOBS") WHERE ROWNUM <= 100` + rewrittenSQL := `SELECT * FROM (SELECT "ID", XMLSERIALIZE(CONTENT "XML_CONTENT" AS CLOB) AS "XML_CONTENT" FROM "DBX"."TEST_LOBS") WHERE ROWNUM <= 100` + calls := []string{} + + result, err := executeOracleSelectWithXMLTypeRetry( + originalSQL, + func(sqlText string) (queryResult, error) { + calls = append(calls, sqlText) + if sqlText == originalSQL { + return queryResult{}, errors.New("abnormal data representation for date") + } + return queryResult{Columns: []string{"ID", "XML_CONTENT"}, Rows: [][]any{{"1", ""}}}, nil + }, + func(sqlText string) (string, error) { + if sqlText != originalSQL { + t.Fatalf("rewrite input = %q, want original SQL", sqlText) + } + return rewrittenSQL, nil + }, + ) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(calls, []string{originalSQL, rewrittenSQL}) { + t.Fatalf("execute calls = %#v, want original and rewritten SQL", calls) + } + if len(result.Rows) != 1 || result.Rows[0][1] != "" { + t.Fatalf("unexpected retry result: %#v", result) + } +} + +func TestExecuteOracleSelectDoesNotRewriteSuccessfulQueries(t *testing.T) { + calls := 0 + rewriteCalled := false + want := queryResult{Columns: []string{"ID"}, Rows: [][]any{{"1"}}} + + result, err := executeOracleSelectWithXMLTypeRetry( + `SELECT ID FROM TEST_TABLE`, + func(string) (queryResult, error) { + calls++ + return want, nil + }, + func(sqlText string) (string, error) { + rewriteCalled = true + return sqlText, nil + }, + ) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, want) { + t.Fatalf("result = %#v, want %#v", result, want) + } + if calls != 1 || rewriteCalled { + t.Fatalf("successful query should not rewrite: calls=%d rewriteCalled=%t", calls, rewriteCalled) + } +} + +func TestExecuteOracleSelectDoesNotRetryOrdinaryErrors(t *testing.T) { + calls := 0 + rewriteCalled := false + originalErr := errors.New("ORA-00942: table or view does not exist") + + _, err := executeOracleSelectWithXMLTypeRetry( + `SELECT * FROM MISSING_TABLE`, + func(string) (queryResult, error) { + calls++ + return queryResult{}, originalErr + }, + func(sqlText string) (string, error) { + rewriteCalled = true + return sqlText, nil + }, + ) + if !errors.Is(err, originalErr) { + t.Fatalf("error = %v, want original error", err) + } + if calls != 1 || rewriteCalled { + t.Fatalf("ordinary error should not retry: calls=%d rewriteCalled=%t", calls, rewriteCalled) + } +} + +func TestExecuteOracleSelectKeepsDecodeErrorWhenNoXMLTypeRewriteApplies(t *testing.T) { + calls := 0 + originalErr := errors.New("TTC error: received code 36 during response reading") + sqlText := `SELECT * FROM TEST_DATES` + + _, err := executeOracleSelectWithXMLTypeRetry( + sqlText, + func(string) (queryResult, error) { + calls++ + return queryResult{}, originalErr + }, + func(input string) (string, error) { + return input, nil + }, + ) + if !errors.Is(err, originalErr) { + t.Fatalf("error = %v, want original error", err) + } + if calls != 1 { + t.Fatalf("unchanged SQL should not retry, got %d executions", calls) + } +} + func TestNormalizeDDLObjectType(t *testing.T) { tests := map[string]string{ "": "",