This is an automated email from the ASF dual-hosted git repository.

thunguo 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 c218fbeb fix: intercept REPLACE INTO statement in AT mode (#1121) 
(#1123)
c218fbeb is described below

commit c218fbebf2f02339821d1f12c8d544aad7946c59
Author: tachibana22 <[email protected]>
AuthorDate: Fri Jun 5 20:54:23 2026 +0800

    fix: intercept REPLACE INTO statement in AT mode (#1121) (#1123)
    
    Co-authored-by: ssshr-66 <[email protected]>
---
 pkg/datasource/sql/exec/at/at_executor.go      |   4 +
 pkg/datasource/sql/exec/at/at_executor_test.go | 123 +++++++++++++++++++++++++
 2 files changed, 127 insertions(+)

diff --git a/pkg/datasource/sql/exec/at/at_executor.go 
b/pkg/datasource/sql/exec/at/at_executor.go
index 053a5250..ca6b1ea3 100644
--- a/pkg/datasource/sql/exec/at/at_executor.go
+++ b/pkg/datasource/sql/exec/at/at_executor.go
@@ -20,6 +20,7 @@ package at
 import (
        "context"
 
+       "github.com/pkg/errors"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/exec"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/parser"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
@@ -68,6 +69,9 @@ func (e *ATExecutor) ExecWithNamedValue(ctx context.Context, 
execCtx *types.Exec
        if !isGlobalTx(ctx) {
                executor = newPlainExecutor(queryParser, execCtx)
        } else {
+               if queryParser.ExecutorType == types.ReplaceIntoExecutor {
+                       return nil, errors.New("NotSupportYetException: AT mode 
currently does not support REPLACE INTO statement")
+               }
                switch queryParser.SQLType {
                case types.SQLTypeInsert:
                        executor = newInsertExecutor(queryParser, execCtx, 
e.hooks)
diff --git a/pkg/datasource/sql/exec/at/at_executor_test.go 
b/pkg/datasource/sql/exec/at/at_executor_test.go
index 31f9cfb0..54b9a584 100644
--- a/pkg/datasource/sql/exec/at/at_executor_test.go
+++ b/pkg/datasource/sql/exec/at/at_executor_test.go
@@ -490,6 +490,129 @@ func TestATExecutors_ExecContext_BeforeHookError(t 
*testing.T) {
        }
 }
 
+func TestATExecutor_ExecWithNamedValue_ReplaceInto_GlobalTx(t *testing.T) {
+       originalIsGlobalTx := isGlobalTx
+       originalParseSQLQuery := parseSQLQuery
+       t.Cleanup(func() {
+               isGlobalTx = originalIsGlobalTx
+               parseSQLQuery = originalParseSQLQuery
+       })
+
+       isGlobalTx = func(ctx context.Context) bool {
+               return true
+       }
+       parseSQLQuery = func(query string) (*types.ParseContext, error) {
+               return &types.ParseContext{
+                       SQLType:      types.SQLTypeInsert,
+                       ExecutorType: types.ReplaceIntoExecutor,
+               }, nil
+       }
+
+       executor := &ATExecutor{}
+       execCtx := &types.ExecContext{
+               Query:       "REPLACE INTO users (id, name) VALUES (?, ?)",
+               NamedValues: []driver.NamedValue{{Name: "", Value: 1}, {Name: 
"", Value: "new"}},
+       }
+
+       callCount := 0
+       callback := func(ctx context.Context, query string, args 
[]driver.NamedValue) (types.ExecResult, error) {
+               callCount++
+               return &mockExecResult{rowsAffected: 1}, nil
+       }
+
+       result, err := executor.ExecWithNamedValue(context.Background(), 
execCtx, callback)
+
+       assert.Error(t, err, "should return error")
+       if err != nil {
+               assert.Contains(t, err.Error(), "NotSupportYetException: AT 
mode currently does not support REPLACE INTO statement")
+       }
+       assert.Nil(t, result, "result should be nil on error")
+       assert.Equal(t, 0, callCount, "callback should not be called because of 
early interception")
+}
+
+func TestATExecutor_ExecWithValue_ReplaceInto_GlobalTx(t *testing.T) {
+       originalIsGlobalTx := isGlobalTx
+       originalParseSQLQuery := parseSQLQuery
+       t.Cleanup(func() {
+               isGlobalTx = originalIsGlobalTx
+               parseSQLQuery = originalParseSQLQuery
+       })
+
+       isGlobalTx = func(ctx context.Context) bool {
+               return true
+       }
+       parseSQLQuery = func(query string) (*types.ParseContext, error) {
+               return &types.ParseContext{
+                       SQLType:      types.SQLTypeInsert,
+                       ExecutorType: types.ReplaceIntoExecutor,
+               }, nil
+       }
+
+       executor := &ATExecutor{}
+       execCtx := &types.ExecContext{
+               Query:  "REPLACE INTO users (id, name) VALUES (?, ?)",
+               Values: []driver.Value{1, "new"},
+       }
+
+       callCount := 0
+       callback := func(ctx context.Context, query string, args 
[]driver.NamedValue) (types.ExecResult, error) {
+               callCount++
+               return &mockExecResult{rowsAffected: 1}, nil
+       }
+
+       result, err := executor.ExecWithValue(context.Background(), execCtx, 
callback)
+
+       assert.Error(t, err, "should return error")
+       if err != nil {
+               assert.Contains(t, err.Error(), "NotSupportYetException: AT 
mode currently does not support REPLACE INTO statement")
+       }
+       assert.Nil(t, result, "result should be nil on error")
+       assert.Equal(t, 0, callCount, "callback should not be called because of 
early interception")
+}
+
+func TestATExecutor_ExecWithNamedValue_ReplaceInto_NonGlobalTx(t *testing.T) {
+       originalIsGlobalTx := isGlobalTx
+       originalParseSQLQuery := parseSQLQuery
+       t.Cleanup(func() {
+               isGlobalTx = originalIsGlobalTx
+               parseSQLQuery = originalParseSQLQuery
+       })
+
+       isGlobalTx = func(ctx context.Context) bool {
+               return false
+       }
+       parseSQLQuery = func(query string) (*types.ParseContext, error) {
+               return &types.ParseContext{
+                       SQLType:      types.SQLTypeInsert,
+                       ExecutorType: types.ReplaceIntoExecutor,
+               }, nil
+       }
+
+       replaceATExecutorFactories(t, &mockExecutor{
+               execContextFunc: func(ctx context.Context, f 
exec.CallbackWithNamedValue) (types.ExecResult, error) {
+                       return f(ctx, "", nil)
+               },
+       })
+
+       executor := &ATExecutor{}
+       execCtx := &types.ExecContext{
+               Query:       "REPLACE INTO users (id, name) VALUES (?, ?)",
+               NamedValues: []driver.NamedValue{{Name: "", Value: 1}, {Name: 
"", Value: "new"}},
+       }
+
+       callCount := 0
+       callback := func(ctx context.Context, query string, args 
[]driver.NamedValue) (types.ExecResult, error) {
+               callCount++
+               return &mockExecResult{rowsAffected: 1}, nil
+       }
+
+       result, err := executor.ExecWithNamedValue(context.Background(), 
execCtx, callback)
+
+       assert.NoError(t, err, "should not return error in non-global tx")
+       assert.NotNil(t, result, "result should not be nil")
+       assert.Equal(t, 1, callCount, "callback should be called exactly once 
(passed through)")
+}
+
 type mockSQLHook struct {
        sqlType         types.SQLType
        beforeCallCount int


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to