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{
"": "",