fix(oracle): retry XMLTYPE queries after decode errors

Closes #5216
This commit is contained in:
t8y2 2026-08-03 23:15:47 +08:00
parent dae150ea74
commit 8481061a03
No known key found for this signature in database
2 changed files with 141 additions and 0 deletions

View File

@ -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

View File

@ -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", "<root/>"}}}, 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] != "<root/>" {
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{
"": "",