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]