This is an automated email from the ASF dual-hosted git repository.
xingyue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git
The following commit(s) were added to refs/heads/master by this push:
new 9a11740a refactor: abstract multi-database XA resource layer with
registry pattern (#1090)
9a11740a is described below
commit 9a11740a49ea547e3282bbd4375f1efc1cfe1734
Author: ThunGuo <[email protected]>
AuthorDate: Sat Apr 11 18:43:04 2026 +0800
refactor: abstract multi-database XA resource layer with registry pattern
(#1090)
* refactor: abstract multi-database XA resource layer with registry pattern
* Update pkg/datasource/sql/types/types_test.go
Co-authored-by: Copilot <[email protected]>
---------
Co-authored-by: Copilot <[email protected]>
---
pkg/datasource/sql/conn_xa.go | 23 +---
pkg/datasource/sql/conn_xa_test.go | 6 +-
pkg/datasource/sql/db.go | 7 +-
pkg/datasource/sql/types/types.go | 8 ++
pkg/datasource/sql/types/types_test.go | 10 +-
pkg/datasource/sql/xa/mysql_xa_connection.go | 40 ++++++-
pkg/datasource/sql/xa/oracle_xa_connection.go | 122 ++++++++++-----------
pkg/datasource/sql/xa/oracle_xa_connection_test.go | 122 ---------------------
pkg/datasource/sql/xa/postgres_xa_connection.go | 106 ++++++++++++++++++
pkg/datasource/sql/xa/xa_resource.go | 66 +++++++++++
pkg/datasource/sql/xa/xa_resource_factory.go | 48 --------
11 files changed, 298 insertions(+), 260 deletions(-)
diff --git a/pkg/datasource/sql/conn_xa.go b/pkg/datasource/sql/conn_xa.go
index f90216e2..5c7d1ba5 100644
--- a/pkg/datasource/sql/conn_xa.go
+++ b/pkg/datasource/sql/conn_xa.go
@@ -23,11 +23,8 @@ import (
"database/sql/driver"
"errors"
"fmt"
- "strings"
"time"
- "github.com/go-sql-driver/mysql"
-
"seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
"seata.apache.org/seata-go/v2/pkg/datasource/sql/xa"
"seata.apache.org/seata-go/v2/pkg/tm"
@@ -43,6 +40,7 @@ type XAConn struct {
tx driver.Tx
xaResource xa.XAResource
+ xaErrorClassifier xa.XAErrorClassifier
xaBranchXid *XABranchXid
xaActive bool
rollBacked bool
@@ -251,6 +249,7 @@ func (c *XAConn) start(ctx context.Context) error {
return fmt.Errorf("create xa xid:%s resoruce err:%w",
c.txCtx.XID, err)
}
c.xaResource = xaResource
+ c.xaErrorClassifier = xa.CreateErrorClassifier(c.dbType)
if err := c.xaResource.Start(ctx, c.xaBranchXid.String(),
xa.TMNoFlags); err != nil {
return fmt.Errorf("xa xid %s resource connection start err:%w",
c.txCtx.XID, err)
@@ -309,7 +308,7 @@ func (c *XAConn) Rollback(ctx context.Context) error {
if err := c.xaResource.End(ctx, c.xaBranchXid.String(),
xa.TMFail); err != nil {
// Handle XAER_RMFAIL exception - check if it's already
ended
//expected error: Error 1399 (XAE07): XAER_RMFAIL: The
command cannot be executed when global transaction is in the IDLE state
- if isXAER_RMFAILAlreadyEnded(err) {
+ if c.xaErrorClassifier.IsAlreadyEnded(err) {
// If already ended, continue with rollback
log.Infof("XA branch already ended, continuing
with rollback for xid: %s", c.txCtx.XID)
} else {
@@ -418,19 +417,3 @@ func (c *XAConn) XaRollback(ctx context.Context, xaXid
XAXid) error {
c.releaseIfNecessary()
return err
}
-
-// isXAER_RMFAILAlreadyEnded checks if the XAER_RMFAIL error indicates the XA
branch is already ended
-// expected error: Error 1399 (XAE07): XAER_RMFAIL: The command cannot be
executed when global transaction is in the IDLE state
-func isXAER_RMFAILAlreadyEnded(err error) bool {
- if err == nil {
- return false
- }
- if mysqlErr, ok := err.(*mysql.MySQLError); ok {
- if mysqlErr.Number == types.ErrCodeXAER_RMFAIL_IDLE {
- return strings.Contains(mysqlErr.Message, "IDLE state")
|| strings.Contains(mysqlErr.Message, "already ended")
- }
- }
- // TODO: handle other DB errors
-
- return false
-}
diff --git a/pkg/datasource/sql/conn_xa_test.go
b/pkg/datasource/sql/conn_xa_test.go
index fb40084a..6d5e26fc 100644
--- a/pkg/datasource/sql/conn_xa_test.go
+++ b/pkg/datasource/sql/conn_xa_test.go
@@ -36,6 +36,7 @@ import (
"seata.apache.org/seata-go/v2/pkg/datasource/sql/exec"
"seata.apache.org/seata-go/v2/pkg/datasource/sql/mock"
"seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
+ "seata.apache.org/seata-go/v2/pkg/datasource/sql/xa"
"seata.apache.org/seata-go/v2/pkg/protocol/branch"
"seata.apache.org/seata-go/v2/pkg/tm"
)
@@ -378,8 +379,9 @@ func TestXAConn_Rollback_XAER_RMFAIL(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := isXAER_RMFAILAlreadyEnded(tt.err); got !=
tt.want {
- t.Errorf("isXAER_RMFAILAlreadyEnded() = %v,
want %v", got, tt.want)
+ classifier := &xa.MysqlXAErrorClassifier{}
+ if got := classifier.IsAlreadyEnded(tt.err); got !=
tt.want {
+
t.Errorf("MysqlXAErrorClassifier.IsAlreadyEnded() = %v, want %v", got, tt.want)
}
})
}
diff --git a/pkg/datasource/sql/db.go b/pkg/datasource/sql/db.go
index 39a85fe2..cc019283 100644
--- a/pkg/datasource/sql/db.go
+++ b/pkg/datasource/sql/db.go
@@ -221,7 +221,7 @@ func (db *DBResource) ConnectionForXA(ctx context.Context,
xaXid XAXid) (*XAConn
if err != nil {
return nil, fmt.Errorf("get xa new connection failure, xid:%s,
err:%v", xaXid.String(), err)
}
- xaResource, err := xa.CreateXAResource(newDriverConn, types.DBTypeMySQL)
+ xaResource, err := xa.CreateXAResource(newDriverConn, db.dbType)
if err != nil {
return nil, fmt.Errorf("create xa resoruce err:%w", err)
}
@@ -230,8 +230,9 @@ func (db *DBResource) ConnectionForXA(ctx context.Context,
xaXid XAXid) (*XAConn
targetConn: newDriverConn,
res: db,
},
- xaBranchXid: XaIdBuild(xaXid.GetGlobalXid(),
xaXid.GetBranchId()),
- xaResource: xaResource,
+ xaBranchXid: XaIdBuild(xaXid.GetGlobalXid(),
xaXid.GetBranchId()),
+ xaResource: xaResource,
+ xaErrorClassifier: xa.CreateErrorClassifier(db.dbType),
}
return xaConn, nil
}
diff --git a/pkg/datasource/sql/types/types.go
b/pkg/datasource/sql/types/types.go
index d85cea32..74598ce1 100644
--- a/pkg/datasource/sql/types/types.go
+++ b/pkg/datasource/sql/types/types.go
@@ -96,6 +96,14 @@ func ParseDBType(driverName string) DBType {
switch strings.ToLower(driverName) {
case "mysql":
return DBTypeMySQL
+ case "postgres", "postgresql", "pgx":
+ return DBTypePostgreSQL
+ case "oracle", "godror", "go-ora":
+ return DBTypeOracle
+ case "sqlserver", "mssql":
+ return DBTypeSQLServer
+ case "mariadb":
+ return DBTypeMARIADB
default:
return DBTypeUnknown
}
diff --git a/pkg/datasource/sql/types/types_test.go
b/pkg/datasource/sql/types/types_test.go
index cd48c16f..b15dbd8e 100644
--- a/pkg/datasource/sql/types/types_test.go
+++ b/pkg/datasource/sql/types/types_test.go
@@ -114,7 +114,15 @@ func TestParseDBType(t *testing.T) {
{"mysql", "mysql", DBTypeMySQL},
{"MySQL uppercase", "MySQL", DBTypeMySQL},
{"MYSQL", "MYSQL", DBTypeMySQL},
- {"postgres", "postgres", DBTypeUnknown},
+ {"postgres", "postgres", DBTypePostgreSQL},
+ {"postgresql", "postgresql", DBTypePostgreSQL},
+ {"pgx", "pgx", DBTypePostgreSQL},
+ {"oracle", "oracle", DBTypeOracle},
+ {"godror", "godror", DBTypeOracle},
+ {"go-ora", "go-ora", DBTypeOracle},
+ {"sqlserver", "sqlserver", DBTypeSQLServer},
+ {"mssql", "mssql", DBTypeSQLServer},
+ {"mariadb", "mariadb", DBTypeMARIADB},
{"unknown", "unknown", DBTypeUnknown},
{"empty", "", DBTypeUnknown},
}
diff --git a/pkg/datasource/sql/xa/mysql_xa_connection.go
b/pkg/datasource/sql/xa/mysql_xa_connection.go
index f60ba492..601a27dd 100644
--- a/pkg/datasource/sql/xa/mysql_xa_connection.go
+++ b/pkg/datasource/sql/xa/mysql_xa_connection.go
@@ -26,17 +26,51 @@ import (
"strings"
"time"
+ "github.com/go-sql-driver/mysql"
+
+ "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
"seata.apache.org/seata-go/v2/pkg/util/log"
)
-type MysqlXAConn struct {
- driver.Conn
+func init() {
+ RegisterXAResourceFactory(types.DBTypeMySQL, &mysqlXAResourceFactory{})
}
-func NewMysqlXaConn(conn driver.Conn) *MysqlXAConn {
+// mysqlXAResourceFactory creates MySQL-specific XA resources and error
classifiers.
+type mysqlXAResourceFactory struct{}
+
+func (f *mysqlXAResourceFactory) CreateXAResource(conn driver.Conn) XAResource
{
return &MysqlXAConn{Conn: conn}
}
+func (f *mysqlXAResourceFactory) CreateErrorClassifier() XAErrorClassifier {
+ return &MysqlXAErrorClassifier{}
+}
+
+// MysqlXAErrorClassifier classifies MySQL-specific XA errors.
+type MysqlXAErrorClassifier struct{}
+
+// IsAlreadyEnded checks if the XAER_RMFAIL error indicates the XA branch is
already ended.
+// Expected error: Error 1399 (XAE07): XAER_RMFAIL: The command cannot be
executed
+// when global transaction is in the IDLE state
+func (c *MysqlXAErrorClassifier) IsAlreadyEnded(err error) bool {
+ if err == nil {
+ return false
+ }
+ var mysqlErr *mysql.MySQLError
+ if errors.As(err, &mysqlErr) {
+ if mysqlErr.Number == types.ErrCodeXAER_RMFAIL_IDLE {
+ return strings.Contains(mysqlErr.Message, "IDLE state")
|| strings.Contains(mysqlErr.Message, "already ended")
+ }
+ }
+ return false
+}
+
+// MysqlXAConn implements XAResource for MySQL using native XA SQL statements.
+type MysqlXAConn struct {
+ driver.Conn
+}
+
func (c *MysqlXAConn) Commit(ctx context.Context, xid string, onePhase bool)
error {
log.Infof("xa branch commit, xid %s", xid)
diff --git a/pkg/datasource/sql/xa/oracle_xa_connection.go
b/pkg/datasource/sql/xa/oracle_xa_connection.go
index df03920f..8ba132a0 100644
--- a/pkg/datasource/sql/xa/oracle_xa_connection.go
+++ b/pkg/datasource/sql/xa/oracle_xa_connection.go
@@ -20,92 +20,92 @@ package xa
import (
"context"
"database/sql/driver"
- "strings"
+ "fmt"
"time"
- _ "github.com/sijms/go-ora/v2"
+ "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
+ "seata.apache.org/seata-go/v2/pkg/util/log"
)
-type OracleXAConn struct {
- driver.Conn
+func init() {
+ RegisterXAResourceFactory(types.DBTypeOracle,
&oracleXAResourceFactory{})
}
-func (c *OracleXAConn) Commit(xid string, onePhase bool) error {
- var sb strings.Builder
- sb.WriteString("XA COMMIT ")
- sb.WriteString(xid)
- if onePhase {
- sb.WriteString(" ONE PHASE")
- }
+type oracleXAResourceFactory struct{}
- conn, _ := c.Conn.(driver.ExecerContext)
- _, err := conn.ExecContext(context.TODO(), sb.String(), nil)
- return err
+func (f *oracleXAResourceFactory) CreateXAResource(conn driver.Conn)
XAResource {
+ return &OracleXAConn{Conn: conn}
}
-func (c *OracleXAConn) End(xid string, flags int) error {
- var sb strings.Builder
- sb.WriteString("XA END ")
- sb.WriteString(xid)
-
- conn, _ := c.Conn.(driver.ExecerContext)
- _, err := conn.ExecContext(context.TODO(), sb.String(), nil)
- return err
+func (f *oracleXAResourceFactory) CreateErrorClassifier() XAErrorClassifier {
+ return &OracleXAErrorClassifier{}
}
-func (c *OracleXAConn) Forget(xid string) error {
- // TODO implement me
- panic("implement me")
+// OracleXAErrorClassifier classifies Oracle-specific XA errors.
+type OracleXAErrorClassifier struct{}
+
+func (c *OracleXAErrorClassifier) IsAlreadyEnded(err error) bool {
+ // TODO: check ORA-24756 (transaction does not exist) / ORA-24761
(rolled back)
+ return false
}
-func (c *OracleXAConn) GetTransactionTimeout() time.Duration {
- // TODO implement me
- panic("implement me")
+// OracleXAConn implements XAResource for Oracle using the DBMS_XA PL/SQL
package.
+//
+// Oracle does NOT support MySQL-style XA SQL statements.
+// All operations go through PL/SQL anonymous blocks calling DBMS_XA:
+// - DBMS_XA.XA_START / XA_END / XA_PREPARE / XA_COMMIT / XA_ROLLBACK
+// - XID type: DBMS_XA_XID(formatid NUMBER, gtrid RAW(64), bqual RAW(64))
+// - Recovery: DBA_PENDING_TRANSACTIONS or DBMS_XA.XA_RECOVER
+// - Requires: GRANT EXECUTE ON DBMS_XA TO <user>
+type OracleXAConn struct {
+ driver.Conn
}
-func (c *OracleXAConn) IsSameRM(resource XAResource) bool {
- // TODO implement me
- panic("implement me")
+func (c *OracleXAConn) Start(ctx context.Context, xid string, flags int) error
{
+ log.Infof("xa branch start (oracle), xid %s", xid)
+ // TODO: DBMS_XA.XA_START via PL/SQL block, map xid to DBMS_XA_XID
+ return fmt.Errorf("Oracle XA start not yet implemented")
}
-func (c *OracleXAConn) XAPrepare(xid string) (int, error) {
- var sb strings.Builder
- sb.WriteString("XA PREPARE ")
- sb.WriteString(xid)
+func (c *OracleXAConn) End(ctx context.Context, xid string, flags int) error {
+ log.Infof("xa branch end (oracle), xid %s", xid)
+ // TODO: DBMS_XA.XA_END via PL/SQL block
+ return fmt.Errorf("Oracle XA end not yet implemented")
+}
- conn, _ := c.Conn.(driver.ExecerContext)
- if _, err := conn.ExecContext(context.TODO(), sb.String(), nil); err !=
nil {
- return -1, err
- }
- return 0, nil
+func (c *OracleXAConn) XAPrepare(ctx context.Context, xid string) error {
+ log.Infof("xa branch prepare (oracle), xid %s", xid)
+ // TODO: DBMS_XA.XA_PREPARE via PL/SQL block
+ return fmt.Errorf("Oracle XA prepare not yet implemented")
}
-func (c *OracleXAConn) Recover(flag int) []string {
- // TODO implement me
- panic("implement me")
+func (c *OracleXAConn) Commit(ctx context.Context, xid string, onePhase bool)
error {
+ log.Infof("xa branch commit (oracle), xid %s, onePhase %v", xid,
onePhase)
+ // TODO: DBMS_XA.XA_COMMIT via PL/SQL block
+ return fmt.Errorf("Oracle XA commit not yet implemented")
}
-func (c *OracleXAConn) Rollback(xid string) error {
- var sb strings.Builder
- sb.WriteString("XA ROLLBACK ")
- sb.WriteString(xid)
+func (c *OracleXAConn) Rollback(ctx context.Context, xid string) error {
+ log.Infof("xa branch rollback (oracle), xid %s", xid)
+ // TODO: DBMS_XA.XA_ROLLBACK via PL/SQL block
+ return fmt.Errorf("Oracle XA rollback not yet implemented")
+}
- conn, _ := c.Conn.(driver.ExecerContext)
- _, err := conn.ExecContext(context.TODO(), sb.String(), nil)
- return err
+func (c *OracleXAConn) Recover(ctx context.Context, flag int) ([]string,
error) {
+ if (flag & TMStartRScan) == 0 {
+ return nil, nil
+ }
+ // TODO: SELECT globalid, branchid FROM DBA_PENDING_TRANSACTIONS
+ return nil, fmt.Errorf("Oracle XA recover not yet implemented")
}
-func (c *OracleXAConn) SetTransactionTimeout(duration time.Duration) bool {
- // TODO implement me
- panic("implement me")
+func (c *OracleXAConn) Forget(ctx context.Context, xid string) error {
+ // TODO: DBMS_XA.XA_FORGET via PL/SQL block
+ return fmt.Errorf("Oracle XA forget not yet implemented")
}
-func (c *OracleXAConn) Start(xid string, flags int) error {
- var sb strings.Builder
- sb.WriteString("XA START")
- sb.WriteString(xid)
+func (c *OracleXAConn) GetTransactionTimeout() time.Duration { return 0 }
- conn, _ := c.Conn.(driver.ExecerContext)
- _, err := conn.ExecContext(context.TODO(), sb.String(), nil)
- return err
-}
+func (c *OracleXAConn) IsSameRM(ctx context.Context, resource XAResource) bool
{ return false }
+
+func (c *OracleXAConn) SetTransactionTimeout(duration time.Duration) bool {
return false }
diff --git a/pkg/datasource/sql/xa/oracle_xa_connection_test.go
b/pkg/datasource/sql/xa/oracle_xa_connection_test.go
deleted file mode 100644
index 6b8471e7..00000000
--- a/pkg/datasource/sql/xa/oracle_xa_connection_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package xa
-
-import (
- "database/sql/driver"
- "testing"
- "time"
-)
-
-func TestOracleXAConn_Commit(t *testing.T) {
- type fields struct {
- Conn driver.Conn
- }
- type args struct {
- xid string
- onePhase bool
- }
- var tests []struct {
- name string
- fields fields
- args args
- wantErr bool
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- c := &OracleXAConn{
- Conn: tt.fields.Conn,
- }
- if err := c.Commit(tt.args.xid, tt.args.onePhase); (err
!= nil) != tt.wantErr {
- t.Errorf("Commit() error = %v, wantErr %v",
err, tt.wantErr)
- }
- })
- }
-}
-
-func TestOracleXAConn_End(t *testing.T) {
- type fields struct {
- Conn driver.Conn
- }
- type args struct {
- xid string
- flags int
- }
- var tests []struct {
- name string
- fields fields
- args args
- wantErr bool
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- c := &OracleXAConn{
- Conn: tt.fields.Conn,
- }
- if err := c.End(tt.args.xid, tt.args.flags); (err !=
nil) != tt.wantErr {
- t.Errorf("End() error = %v, wantErr %v", err,
tt.wantErr)
- }
- })
- }
-}
-
-func TestOracleXAConn_Forget(t *testing.T) {
- type fields struct {
- Conn driver.Conn
- }
- type args struct {
- xid string
- }
- var tests []struct {
- name string
- fields fields
- args args
- wantErr bool
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- c := &OracleXAConn{
- Conn: tt.fields.Conn,
- }
- if err := c.Forget(tt.args.xid); (err != nil) !=
tt.wantErr {
- t.Errorf("Forget() error = %v, wantErr %v",
err, tt.wantErr)
- }
- })
- }
-}
-
-func TestOracleXAConn_GetTransactionTimeout(t *testing.T) {
- type fields struct {
- Conn driver.Conn
- }
- var tests []struct {
- name string
- fields fields
- want time.Duration
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- c := &OracleXAConn{
- Conn: tt.fields.Conn,
- }
- if got := c.GetTransactionTimeout(); got != tt.want {
- t.Errorf("GetTransactionTimeout() = %v, want
%v", got, tt.want)
- }
- })
- }
-}
diff --git a/pkg/datasource/sql/xa/postgres_xa_connection.go
b/pkg/datasource/sql/xa/postgres_xa_connection.go
new file mode 100644
index 00000000..a1a0fb51
--- /dev/null
+++ b/pkg/datasource/sql/xa/postgres_xa_connection.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xa
+
+import (
+ "context"
+ "database/sql/driver"
+ "fmt"
+ "time"
+
+ "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
+ "seata.apache.org/seata-go/v2/pkg/util/log"
+)
+
+func init() {
+ RegisterXAResourceFactory(types.DBTypePostgreSQL,
&postgresXAResourceFactory{})
+}
+
+type postgresXAResourceFactory struct{}
+
+func (f *postgresXAResourceFactory) CreateXAResource(conn driver.Conn)
XAResource {
+ return &PostgresXAConn{Conn: conn}
+}
+
+func (f *postgresXAResourceFactory) CreateErrorClassifier() XAErrorClassifier {
+ return &PostgresXAErrorClassifier{}
+}
+
+// PostgresXAErrorClassifier classifies PostgreSQL-specific XA errors.
+type PostgresXAErrorClassifier struct{}
+
+func (c *PostgresXAErrorClassifier) IsAlreadyEnded(err error) bool {
+ // TODO: check pgconn.PgError SQLSTATE "42704" / "55000"
+ return false
+}
+
+// PostgresXAConn implements XAResource for PostgreSQL using native 2PC.
+//
+// Key differences from MySQL XA:
+// - Start/End are no-ops (PostgreSQL uses regular BEGIN, no XA START/END).
+// - XAPrepare → PREPARE TRANSACTION 'xid'
+// - Commit → COMMIT PREPARED 'xid'
+// - Rollback → ROLLBACK PREPARED 'xid'
+// - Recover → SELECT gid FROM pg_prepared_xacts
+// - Requires max_prepared_transactions > 0 in postgresql.conf.
+type PostgresXAConn struct {
+ driver.Conn
+}
+
+func (c *PostgresXAConn) Start(ctx context.Context, xid string, flags int)
error {
+ log.Infof("xa branch start (postgres no-op), xid %s", xid)
+ return nil
+}
+
+func (c *PostgresXAConn) End(ctx context.Context, xid string, flags int) error
{
+ log.Infof("xa branch end (postgres no-op), xid %s", xid)
+ return nil
+}
+
+func (c *PostgresXAConn) XAPrepare(ctx context.Context, xid string) error {
+ // TODO: PREPARE TRANSACTION 'xid'
+ return fmt.Errorf("PostgreSQL XA prepare not yet implemented")
+}
+
+func (c *PostgresXAConn) Commit(ctx context.Context, xid string, onePhase
bool) error {
+ // TODO: COMMIT PREPARED 'xid' (onePhase=true → regular commit by upper
layer)
+ return fmt.Errorf("PostgreSQL XA commit not yet implemented")
+}
+
+func (c *PostgresXAConn) Rollback(ctx context.Context, xid string) error {
+ // TODO: ROLLBACK PREPARED 'xid'
+ return fmt.Errorf("PostgreSQL XA rollback not yet implemented")
+}
+
+func (c *PostgresXAConn) Recover(ctx context.Context, flag int) ([]string,
error) {
+ if (flag & TMStartRScan) == 0 {
+ return nil, nil
+ }
+ // TODO: SELECT gid FROM pg_prepared_xacts WHERE database =
current_database()
+ return nil, fmt.Errorf("PostgreSQL XA recover not yet implemented")
+}
+
+func (c *PostgresXAConn) Forget(ctx context.Context, xid string) error {
+ return fmt.Errorf("PostgreSQL does not support XA forget")
+}
+
+func (c *PostgresXAConn) GetTransactionTimeout() time.Duration { return 0 }
+
+func (c *PostgresXAConn) IsSameRM(ctx context.Context, resource XAResource)
bool { return false }
+
+func (c *PostgresXAConn) SetTransactionTimeout(duration time.Duration) bool {
return false }
diff --git a/pkg/datasource/sql/xa/xa_resource.go
b/pkg/datasource/sql/xa/xa_resource.go
index db26a643..c37fc118 100644
--- a/pkg/datasource/sql/xa/xa_resource.go
+++ b/pkg/datasource/sql/xa/xa_resource.go
@@ -19,7 +19,11 @@ package xa
import (
"context"
+ "database/sql/driver"
+ "fmt"
"time"
+
+ "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
)
const (
@@ -57,6 +61,8 @@ const (
XAOk = 0
)
+// XAResource defines the contract for XA transaction operations across
different databases.
+// Each database (MySQL, PostgreSQL, Oracle, etc.) provides its own
implementation.
type XAResource interface {
Commit(ctx context.Context, xid string, onePhase bool) error
End(ctx context.Context, xid string, flags int) error
@@ -69,3 +75,63 @@ type XAResource interface {
SetTransactionTimeout(duration time.Duration) bool
Start(ctx context.Context, xid string, flags int) error
}
+
+// XAErrorClassifier abstracts database-specific XA error classification.
+// This allows the upper layer (conn_xa.go) to handle XA errors without
+// importing database-specific driver packages.
+type XAErrorClassifier interface {
+ // IsAlreadyEnded checks if the error indicates the XA branch is
already ended.
+ // For MySQL: XAER_RMFAIL with IDLE state (error 1399).
+ // For PostgreSQL: transaction already committed/rolled back.
+ // For Oracle: ORA-24756 (transaction does not exist).
+ IsAlreadyEnded(err error) bool
+}
+
+// defaultErrorClassifier is a no-op classifier that never matches any error.
+// Used as a fallback when no database-specific classifier is registered.
+type defaultErrorClassifier struct{}
+
+func (c *defaultErrorClassifier) IsAlreadyEnded(err error) bool { return false
}
+
+// XAResourceFactory creates database-specific XA resources and error
classifiers.
+type XAResourceFactory interface {
+ // CreateXAResource creates a new XAResource for the given driver
connection.
+ CreateXAResource(conn driver.Conn) XAResource
+ // CreateErrorClassifier creates a database-specific error classifier.
+ CreateErrorClassifier() XAErrorClassifier
+}
+
+// registry holds registered XAResourceFactory instances per DBType.
+var registry = map[types.DBType]XAResourceFactory{}
+
+// RegisterXAResourceFactory registers a factory for the given database type.
+// Each database driver package should call this in its init() function.
+func RegisterXAResourceFactory(dbType types.DBType, factory XAResourceFactory)
{
+ registry[dbType] = factory
+}
+
+// GetXAResourceFactory returns the registered factory for the given database
type.
+func GetXAResourceFactory(dbType types.DBType) (XAResourceFactory, bool) {
+ f, ok := registry[dbType]
+ return f, ok
+}
+
+// CreateXAResource creates an XAResource for the given database type and
connection.
+// It uses the registered factory for the database type.
+func CreateXAResource(conn driver.Conn, dbType types.DBType) (XAResource,
error) {
+ factory, ok := GetXAResourceFactory(dbType)
+ if !ok {
+ return nil, fmt.Errorf("no XA resource factory registered for
db type: %s", dbType.String())
+ }
+ return factory.CreateXAResource(conn), nil
+}
+
+// CreateErrorClassifier creates an XAErrorClassifier for the given database
type.
+// Returns a default no-op classifier if no factory is registered.
+func CreateErrorClassifier(dbType types.DBType) XAErrorClassifier {
+ factory, ok := GetXAResourceFactory(dbType)
+ if !ok {
+ return &defaultErrorClassifier{}
+ }
+ return factory.CreateErrorClassifier()
+}
diff --git a/pkg/datasource/sql/xa/xa_resource_factory.go
b/pkg/datasource/sql/xa/xa_resource_factory.go
deleted file mode 100644
index ac6c1a6e..00000000
--- a/pkg/datasource/sql/xa/xa_resource_factory.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package xa
-
-import (
- "database/sql/driver"
- "fmt"
-
- "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
- "seata.apache.org/seata-go/v2/pkg/util/log"
-)
-
-// CreateXAResource create a connection for xa with the different db type.
-// Such as mysql, oracle, MARIADB, POSTGRESQL
-func CreateXAResource(conn driver.Conn, dbType types.DBType) (XAResource,
error) {
- var err error
- var xaConnection XAResource
- switch dbType {
- case types.DBTypeMySQL:
- xaConnection = NewMysqlXaConn(conn)
- case types.DBTypeOracle:
- case types.DBTypePostgreSQL:
- default:
- err = fmt.Errorf("not support db type for :%s", dbType.String())
- }
-
- if err != nil {
- log.Errorf(err.Error())
- return nil, err
- }
-
- return xaConnection, nil
-}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]