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

flypiggy 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 1b526c36 fix: resolve undo_log duplicate PK when column names have 
escape characters (#1065)
1b526c36 is described below

commit 1b526c361555fcb6d7ef50221ba296bc022ae674
Author: ThunGuo <[email protected]>
AuthorDate: Sun Mar 22 22:15:47 2026 +0800

    fix: resolve undo_log duplicate PK when column names have escape characters 
(#1065)
    
    Co-authored-by: FengZhang <[email protected]>
---
 pkg/datasource/sql/datasource/mysql/trigger.go     |   6 +-
 pkg/datasource/sql/exec/at/base_executor.go        |  19 +-
 pkg/datasource/sql/exec/at/base_executor_test.go   | 111 +++++++++++
 pkg/datasource/sql/exec/at/escape.go               | 210 ---------------------
 pkg/datasource/sql/exec/at/escape_test.go          |  56 ------
 pkg/datasource/sql/exec/at/insert_executor.go      |   8 +-
 pkg/datasource/sql/exec/at/insert_executor_test.go |  35 ++++
 .../sql/exec/at/insert_on_update_executor.go       |   8 +-
 .../sql/undo/builder/basic_undo_log_builder.go     |  11 +-
 .../undo/builder/mysql_insert_undo_log_builder.go  |   6 +-
 ...ql_insertonduplicate_update_undo_log_builder.go |   4 +-
 pkg/datasource/sql/undo/executor/executor.go       |   4 +-
 pkg/datasource/sql/undo/executor/executor_test.go  |  49 +++++
 .../undo/executor/mysql_undo_delete_executor.go    |   7 +-
 .../executor/mysql_undo_delete_executor_test.go    |   9 +-
 .../undo/executor/mysql_undo_insert_executor.go    |   5 +-
 .../executor/mysql_undo_insert_executor_test.go    |  28 ++-
 .../undo/executor/mysql_undo_update_executor.go    |   9 +-
 .../executor/mysql_undo_update_executor_test.go    |  13 +-
 pkg/datasource/sql/undo/executor/utils.go          |  14 +-
 pkg/datasource/sql/undo/executor/utils_test.go     |  53 ++++++
 .../sql/{undo/executor/sql.go => util/escape.go}   |   8 +-
 .../executor/sql_test.go => util/escape_test.go}   |  16 +-
 pkg/datasource/sql/util/lockkey.go                 |   3 +-
 pkg/datasource/sql/util/lockkey_test.go            |  68 +++++++
 25 files changed, 429 insertions(+), 331 deletions(-)

diff --git a/pkg/datasource/sql/datasource/mysql/trigger.go 
b/pkg/datasource/sql/datasource/mysql/trigger.go
index 1d9f0ee1..a14189c5 100644
--- a/pkg/datasource/sql/datasource/mysql/trigger.go
+++ b/pkg/datasource/sql/datasource/mysql/trigger.go
@@ -26,7 +26,7 @@ import (
        "github.com/pkg/errors"
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo/executor"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 type mysqlTrigger struct {
@@ -99,7 +99,7 @@ func (m *mysqlTrigger) getColumnMetas(ctx context.Context, 
dbName string, table
                return m.getColumnMetasFn(ctx, dbName, table, conn)
        }
 
-       table = executor.DelEscape(table, types.DBTypeMySQL)
+       table = util.DelEscape(table, types.DBTypeMySQL)
        var columnMetas []types.ColumnMeta
 
        columnMetaSql := "SELECT `TABLE_NAME`, `TABLE_SCHEMA`, `COLUMN_NAME`, 
`DATA_TYPE`, `COLUMN_TYPE`, `COLUMN_KEY`, `IS_NULLABLE`, `COLUMN_DEFAULT`, 
`EXTRA` FROM INFORMATION_SCHEMA.COLUMNS WHERE `TABLE_SCHEMA` = ? AND 
`TABLE_NAME` = ?"
@@ -175,7 +175,7 @@ func (m *mysqlTrigger) getIndexes(ctx context.Context, 
dbName string, tableName
                return m.getIndexesFn(ctx, dbName, tableName, conn)
        }
 
-       tableName = executor.DelEscape(tableName, types.DBTypeMySQL)
+       tableName = util.DelEscape(tableName, types.DBTypeMySQL)
        result := make([]types.IndexMeta, 0)
 
        indexMetaSql := "SELECT `INDEX_NAME`, `COLUMN_NAME`, `NON_UNIQUE` FROM 
`INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
diff --git a/pkg/datasource/sql/exec/at/base_executor.go 
b/pkg/datasource/sql/exec/at/base_executor.go
index 6e031488..c34654e2 100644
--- a/pkg/datasource/sql/exec/at/base_executor.go
+++ b/pkg/datasource/sql/exec/at/base_executor.go
@@ -242,10 +242,11 @@ func (b *baseExecutor) buildRecordImages(rowsi 
driver.Rows, tableMetaData *types
                columns := make([]types.ColumnImage, 0)
                // build record image
                for i, name := range columnNames {
-                       columnMeta := tableMetaData.Columns[name]
+                       cleanName := util.DelEscape(name, types.DBTypeMySQL)
+                       columnMeta := tableMetaData.Columns[cleanName]
 
                        keyType := types.IndexTypeNull
-                       if _, ok := tableMetaData.GetPrimaryKeyMap()[name]; ok {
+                       if _, ok := 
tableMetaData.GetPrimaryKeyMap()[cleanName]; ok {
                                keyType = types.IndexTypePrimaryKey
                        }
                        jdbcType := 
types.MySQLStrToJavaType(columnMeta.DatabaseTypeString)
@@ -281,7 +282,7 @@ func (b *baseExecutor) getNeedColumns(meta 
*types.TableMeta, columns []string, d
        }
 
        for i := range needUpdateColumns {
-               needUpdateColumns[i] = AddEscape(needUpdateColumns[i], dbType)
+               needUpdateColumns[i] = util.AddEscape(needUpdateColumns[i], 
dbType)
        }
        return needUpdateColumns
 }
@@ -294,9 +295,9 @@ func (b *baseExecutor) containsPKByName(meta 
*types.TableMeta, columns []string)
 
        matchCounter := 0
        for _, column := range columns {
+               cleanColumn := util.DelEscape(column, types.DBTypeMySQL)
                for _, pkName := range pkColumnNameList {
-                       if strings.EqualFold(pkName, column) ||
-                               strings.EqualFold(pkName, 
strings.ToLower(column)) {
+                       if strings.EqualFold(pkName, cleanColumn) {
                                matchCounter++
                        }
                }
@@ -479,8 +480,14 @@ func (b *baseExecutor) buildPKParams(rows 
[]types.RowImage, pkNameList []string)
        params := make([]driver.NamedValue, 0)
        for _, row := range rows {
                coumnMap := row.GetColumnMap()
+               // Build a normalized map with escaped characters removed
+               normalizedMap := make(map[string]*types.ColumnImage, 
len(coumnMap))
+               for k, v := range coumnMap {
+                       normalizedMap[util.DelEscape(k, types.DBTypeMySQL)] = v
+               }
                for i, pk := range pkNameList {
-                       if col, ok := coumnMap[pk]; ok {
+                       cleanPK := util.DelEscape(pk, types.DBTypeMySQL)
+                       if col, ok := normalizedMap[cleanPK]; ok {
                                params = append(params, driver.NamedValue{
                                        Ordinal: i, Value: col.Value,
                                })
diff --git a/pkg/datasource/sql/exec/at/base_executor_test.go 
b/pkg/datasource/sql/exec/at/base_executor_test.go
index 9d1d52a4..29843831 100644
--- a/pkg/datasource/sql/exec/at/base_executor_test.go
+++ b/pkg/datasource/sql/exec/at/base_executor_test.go
@@ -211,3 +211,114 @@ func TestBaseExecBuildLockKey(t *testing.T) {
                })
        }
 }
+
+func TestBaseExecContainsPKByName_EscapedColumns(t *testing.T) {
+       meta := &types.TableMeta{
+               Indexs: map[string]types.IndexMeta{
+                       "PRIMARY": {
+                               IType: types.IndexTypePrimaryKey,
+                               Columns: []types.ColumnMeta{
+                                       {ColumnName: "id"},
+                               },
+                       },
+               },
+       }
+
+       tests := []struct {
+               name    string
+               columns []string
+               want    bool
+       }{
+               {
+                       name:    "plain column matches PK",
+                       columns: []string{"id", "name"},
+                       want:    true,
+               },
+               {
+                       name:    "backtick-escaped column matches PK",
+                       columns: []string{"`id`", "name"},
+                       want:    true,
+               },
+               {
+                       name:    "all backtick-escaped columns",
+                       columns: []string{"`id`", "`name`"},
+                       want:    true,
+               },
+               {
+                       name:    "case-insensitive backtick-escaped",
+                       columns: []string{"`ID`", "`NAME`"},
+                       want:    true,
+               },
+               {
+                       name:    "no PK in columns",
+                       columns: []string{"`name`", "`age`"},
+                       want:    false,
+               },
+       }
+
+       var exec baseExecutor
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       result := exec.containsPKByName(meta, tt.columns)
+                       assert.Equal(t, tt.want, result)
+               })
+       }
+}
+
+func TestBaseExecBuildLockKey_EscapedColumnNames(t *testing.T) {
+       var exec baseExecutor
+
+       getColumnImage := func(columnName string, value interface{}) 
types.ColumnImage {
+               return types.ColumnImage{KeyType: types.IndexTypePrimaryKey, 
ColumnName: columnName, Value: value}
+       }
+
+       tests := []struct {
+               name     string
+               metaData types.TableMeta
+               records  types.RecordImage
+               expected string
+       }{
+               {
+                       name: "Backtick-escaped single PK",
+                       metaData: types.TableMeta{
+                               TableName: "test_table",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: []types.ColumnMeta{{ColumnName: "id"}}},
+                               },
+                       },
+                       records: types.RecordImage{
+                               TableName: "test_table",
+                               Rows: []types.RowImage{
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("`id`", 1), {ColumnName: "`name`", Value: 
"test"}}},
+                               },
+                       },
+                       expected: "TEST_TABLE:1",
+               },
+               {
+                       name: "Backtick-escaped composite PK",
+                       metaData: types.TableMeta{
+                               TableName: "orders",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: []types.ColumnMeta{
+                                               {ColumnName: "order_id"},
+                                               {ColumnName: "user_id"},
+                                       }},
+                               },
+                       },
+                       records: types.RecordImage{
+                               TableName: "orders",
+                               Rows: []types.RowImage{
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("`order_id`", 100), 
getColumnImage("`user_id`", 1)}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("`order_id`", 200), 
getColumnImage("`user_id`", 2)}},
+                               },
+                       },
+                       expected: "ORDERS:100_1,200_2",
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       lockKeys := exec.buildLockKey(&tt.records, tt.metaData)
+                       assert.Equal(t, tt.expected, lockKeys)
+               })
+       }
+}
diff --git a/pkg/datasource/sql/exec/at/escape.go 
b/pkg/datasource/sql/exec/at/escape.go
deleted file mode 100644
index 676959d2..00000000
--- a/pkg/datasource/sql/exec/at/escape.go
+++ /dev/null
@@ -1,210 +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 at
-
-import (
-       "database/sql"
-       "strings"
-
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
-)
-
-const (
-       dot            = "."
-       escapeStandard = "\""
-       escapeMysql    = "`"
-)
-
-// DelEscape del escape by db type
-func DelEscape(colName string, dbType types.DBType) string {
-       newColName := delEscape(colName, escapeStandard)
-       if dbType == types.DBTypeMySQL {
-               newColName = delEscape(newColName, escapeMysql)
-       }
-       return newColName
-}
-
-// delEscape
-func delEscape(colName string, escape string) string {
-       if colName == "" {
-               return ""
-       }
-
-       if string(colName[0]) == escape && string(colName[len(colName)-1]) == 
escape {
-               // like "scheme"."id" `scheme`.`id`
-               str := escape + dot + escape
-               index := strings.Index(colName, str)
-               if index > -1 {
-                       return colName[1:index] + dot + 
colName[index+len(str):len(colName)-1]
-               }
-
-               return colName[1 : len(colName)-1]
-       } else {
-               // like "scheme".id `scheme`.id
-               str := escape + dot
-               index := strings.Index(colName, str)
-               if index > -1 && string(colName[0]) == escape {
-                       return colName[1:index] + dot + colName[index+len(str):]
-               }
-
-               // like scheme."id" scheme.`id`
-               str = dot + escape
-               index = strings.Index(colName, str)
-               if index > -1 && string(colName[len(colName)-1]) == escape {
-                       return colName[0:index] + dot + 
colName[index+len(str):len(colName)-1]
-               }
-       }
-
-       return colName
-}
-
-// AddEscape if necessary, add escape by db type
-func AddEscape(colName string, dbType types.DBType) string {
-       if dbType == types.DBTypeMySQL {
-               return addEscape(colName, dbType, escapeMysql)
-       }
-
-       return addEscape(colName, dbType, escapeStandard)
-}
-
-func addEscape(colName string, dbType types.DBType, escape string) string {
-       if colName == "" {
-               return colName
-       }
-
-       if string(colName[0]) == escape && string(colName[len(colName)-1]) == 
escape {
-               return colName
-       }
-
-       if !checkEscape(colName, dbType) {
-               return colName
-       }
-
-       if strings.Contains(colName, dot) {
-               // like "scheme".id `scheme`.id
-               str := escape + dot
-               dotIndex := strings.Index(colName, str)
-               if dotIndex > -1 {
-                       tempStr := strings.Builder{}
-                       tempStr.WriteString(colName[0 : dotIndex+len(str)])
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(colName[dotIndex+len(str):])
-                       tempStr.WriteString(escape)
-
-                       return tempStr.String()
-               }
-
-               // like scheme."id" scheme.`id`
-               str = dot + escape
-               dotIndex = strings.Index(colName, str)
-               if dotIndex > -1 {
-                       tempStr := strings.Builder{}
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(colName[0:dotIndex])
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(colName[dotIndex:])
-
-                       return tempStr.String()
-               }
-
-               str = dot
-               dotIndex = strings.Index(colName, str)
-               if dotIndex > -1 {
-                       tempStr := strings.Builder{}
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(colName[0:dotIndex])
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(dot)
-                       tempStr.WriteString(escape)
-                       tempStr.WriteString(colName[dotIndex+len(str):])
-                       tempStr.WriteString(escape)
-
-                       return tempStr.String()
-               }
-       }
-
-       buf := make([]byte, len(colName)+2)
-       buf[0], buf[len(buf)-1] = escape[0], escape[0]
-
-       for key := range colName {
-               buf[key+1] = colName[key]
-       }
-
-       return string(buf)
-}
-
-// checkEscape check whether given field or table name use keywords. the 
method has database special logic.
-func checkEscape(colName string, dbType types.DBType) bool {
-       switch dbType {
-       case types.DBTypeMySQL:
-               if _, ok := types.GetMysqlKeyWord()[strings.ToUpper(colName)]; 
ok {
-                       return true
-               }
-
-               return false
-       // TODO impl Oracle PG SQLServer ...
-       default:
-               return true
-       }
-}
-
-// BuildWhereConditionByPKs each pk is a condition.the result will like :" id 
=? and userCode =?"
-func BuildWhereConditionByPKs(pkNameList []string, dbType types.DBType) string 
{
-       whereStr := strings.Builder{}
-       for i := 0; i < len(pkNameList); i++ {
-               if i > 0 {
-                       whereStr.WriteString(" and ")
-               }
-
-               pkName := pkNameList[i]
-               whereStr.WriteString(AddEscape(pkName, dbType))
-               whereStr.WriteString(" = ? ")
-       }
-
-       return whereStr.String()
-}
-
-// DataValidationAndGoOn check data valid
-// Todo implement dataValidationAndGoOn
-func DataValidationAndGoOn(sqlUndoLog undo.SQLUndoLog, conn *sql.Conn) bool {
-       return true
-}
-
-func GetOrderedPkList(image *types.RecordImage, row types.RowImage, dbType 
types.DBType) ([]types.ColumnImage, error) {
-
-       pkColumnNameListByOrder := image.TableMeta.GetPrimaryKeyOnlyName()
-
-       pkColumnNameListNoOrder := make([]types.ColumnImage, 0)
-       pkFields := make([]types.ColumnImage, 0)
-
-       for _, column := range row.PrimaryKeys(row.Columns) {
-               column.ColumnName = DelEscape(column.ColumnName, dbType)
-               pkColumnNameListNoOrder = append(pkColumnNameListNoOrder, 
column)
-       }
-
-       for _, pkName := range pkColumnNameListByOrder {
-               for _, col := range pkColumnNameListNoOrder {
-                       if strings.Index(col.ColumnName, pkName) > -1 {
-                               pkFields = append(pkFields, col)
-                       }
-               }
-       }
-
-       return pkFields, nil
-}
diff --git a/pkg/datasource/sql/exec/at/escape_test.go 
b/pkg/datasource/sql/exec/at/escape_test.go
deleted file mode 100644
index 2481810d..00000000
--- a/pkg/datasource/sql/exec/at/escape_test.go
+++ /dev/null
@@ -1,56 +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 at
-
-import (
-       "log"
-       "testing"
-
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
-
-       "github.com/stretchr/testify/assert"
-)
-
-// TestDelEscape
-func TestDelEscape(t *testing.T) {
-       strSlice := []string{`"scheme"."id"`, "`scheme`.`id`", `"scheme".id`, 
`scheme."id"`, `scheme."id"`, "scheme.`id`"}
-
-       for k, v := range strSlice {
-               res := DelEscape(v, types.DBTypeMySQL)
-               log.Printf("val_%d: %s, res_%d: %s\n", k, v, k, res)
-               assert.Equal(t, "scheme.id", res)
-       }
-}
-
-// TestAddEscape
-func TestAddEscape(t *testing.T) {
-       strSlice := []string{`"scheme".id`, "`scheme`.id", `scheme."id"`, 
"scheme.`id`"}
-
-       for k, v := range strSlice {
-               res := AddEscape(v, types.DBTypeMySQL)
-               log.Printf("val_%d: %s, res_%d: %s\n", k, v, k, res)
-               assert.Equal(t, v, res)
-       }
-
-       strSlice1 := []string{"ALTER", "ANALYZE"}
-       for k, v := range strSlice1 {
-               res := AddEscape(v, types.DBTypeMySQL)
-               log.Printf("val_%d: %s, res_%d: %s\n", k, v, k, res)
-               assert.Equal(t, "`"+v+"`", res)
-       }
-}
diff --git a/pkg/datasource/sql/exec/at/insert_executor.go 
b/pkg/datasource/sql/exec/at/insert_executor.go
index 30f3d60b..12e83c64 100644
--- a/pkg/datasource/sql/exec/at/insert_executor.go
+++ b/pkg/datasource/sql/exec/at/insert_executor.go
@@ -258,9 +258,9 @@ func (i *insertExecutor) containsPK(meta types.TableMeta, 
parseCtx *types.ParseC
 
        matchCounter := 0
        for _, column := range parseCtx.InsertStmt.Columns {
+               cleanName := util.DelEscape(column.Name.O, types.DBTypeMySQL)
                for _, pkName := range pkColumnNameList {
-                       if strings.EqualFold(pkName, column.Name.O) ||
-                               strings.EqualFold(pkName, column.Name.L) {
+                       if strings.EqualFold(pkName, cleanName) {
                                matchCounter++
                        }
                }
@@ -271,7 +271,7 @@ func (i *insertExecutor) containsPK(meta types.TableMeta, 
parseCtx *types.ParseC
 
 // containPK compare column name and primary key name
 func (i *insertExecutor) containPK(columnName string, meta types.TableMeta) 
bool {
-       newColumnName := DelEscape(columnName, types.DBTypeMySQL)
+       newColumnName := util.DelEscape(columnName, types.DBTypeMySQL)
        pkColumnNameList := meta.GetPrimaryKeyOnlyName()
        if len(pkColumnNameList) == 0 {
                return false
@@ -314,7 +314,7 @@ func (i *insertExecutor) getPkIndex(InsertStmt 
*ast.InsertStmt, meta types.Table
                tmpColumnMeta := columnMeta
                pkIndex++
                if i.containPK(tmpColumnMeta.ColumnName, meta) {
-                       pkIndexMap[DelEscape(tmpColumnMeta.ColumnName, 
types.DBTypeMySQL)] = pkIndex
+                       pkIndexMap[util.DelEscape(tmpColumnMeta.ColumnName, 
types.DBTypeMySQL)] = pkIndex
                }
        }
 
diff --git a/pkg/datasource/sql/exec/at/insert_executor_test.go 
b/pkg/datasource/sql/exec/at/insert_executor_test.go
index d745dc75..ea57969e 100644
--- a/pkg/datasource/sql/exec/at/insert_executor_test.go
+++ b/pkg/datasource/sql/exec/at/insert_executor_test.go
@@ -217,6 +217,41 @@ func TestMySQLInsertUndoLogBuilder_containsPK(t 
*testing.T) {
                                Columns: []*ast.ColumnName{{}},
                        },
                }}, want: false},
+               {name: "test-escaped-backtick-true", fields: fields{}, args: 
args{meta: types.TableMeta{
+                       Indexs: map[string]types.IndexMeta{
+                               "id": {
+                                       IType: types.IndexTypePrimaryKey,
+                                       Columns: []types.ColumnMeta{{
+                                               ColumnName: "id",
+                                       }},
+                               },
+                       },
+               }, parseCtx: &types.ParseContext{
+                       InsertStmt: &ast.InsertStmt{
+                               Columns: []*ast.ColumnName{{
+                                       Name: model.CIStr{O: "`id`", L: "`id`"},
+                               }, {
+                                       Name: model.CIStr{O: "`name`", L: 
"`name`"},
+                               }},
+                       },
+               }}, want: true},
+               // Issue #702: mixed escaped and unescaped columns
+               {name: "test-mixed-escape-true", fields: fields{}, args: 
args{meta: types.TableMeta{
+                       Indexs: map[string]types.IndexMeta{
+                               "id": {
+                                       IType: types.IndexTypePrimaryKey,
+                                       Columns: []types.ColumnMeta{{
+                                               ColumnName: "id",
+                                       }},
+                               },
+                       },
+               }, parseCtx: &types.ParseContext{
+                       InsertStmt: &ast.InsertStmt{
+                               Columns: []*ast.ColumnName{{
+                                       Name: model.CIStr{O: "`id`", L: "`id`"},
+                               }},
+                       },
+               }}, want: true},
        }
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/datasource/sql/exec/at/insert_on_update_executor.go 
b/pkg/datasource/sql/exec/at/insert_on_update_executor.go
index b935ed65..ada89562 100644
--- a/pkg/datasource/sql/exec/at/insert_on_update_executor.go
+++ b/pkg/datasource/sql/exec/at/insert_on_update_executor.go
@@ -217,15 +217,15 @@ func (i *insertOnUpdateExecutor) 
buildBeforeImageSQLParameters(insertStmt *ast.I
                        return nil, 0, fmt.Errorf("invalid insert row's column 
size")
                }
                for i, col := range insertColumns {
-                       columnName := DelEscape(col, types.DBTypeMySQL)
+                       columnName := util.DelEscape(col, types.DBTypeMySQL)
                        val := rowColumns[i]
                        rStr, ok := val.(string)
                        if ok && strings.EqualFold(rStr, sqlPlaceholder) {
                                objects := args[placeHolderIndex]
-                               parameterMap[columnName] = 
append(parameterMap[col], objects)
+                               parameterMap[columnName] = 
append(parameterMap[columnName], objects)
                                placeHolderIndex++
                        } else {
-                               parameterMap[columnName] = 
append(parameterMap[col], driver.NamedValue{
+                               parameterMap[columnName] = 
append(parameterMap[columnName], driver.NamedValue{
                                        Ordinal: i + 1,
                                        Name:    columnName,
                                        Value:   val,
@@ -315,7 +315,7 @@ func (i *insertOnUpdateExecutor) 
buildAfterImageSQL(beforeImage *types.RecordIma
 
 // isPKColumn check the column name to see if it is a primary key column
 func (i *insertOnUpdateExecutor) isPKColumn(columnName string, meta 
types.TableMeta) bool {
-       newColumnName := DelEscape(columnName, types.DBTypeMySQL)
+       newColumnName := util.DelEscape(columnName, types.DBTypeMySQL)
        pkColumnNameList := meta.GetPrimaryKeyOnlyName()
        if len(pkColumnNameList) == 0 {
                return false
diff --git a/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go 
b/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
index 7ed71262..ac031f47 100644
--- a/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
+++ b/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
@@ -29,9 +29,8 @@ import (
        "github.com/arana-db/parser/test_driver"
        gxsort "github.com/dubbogo/gost/sort"
 
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
-
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 // todo the executor should be stateful
@@ -233,8 +232,14 @@ func (b *BasicUndoLogBuilder) buildPKParams(rows 
[]types.RowImage, pkNameList []
        params := make([]driver.Value, 0)
        for _, row := range rows {
                coumnMap := row.GetColumnMap()
+               // Build a normalized map with escaped characters removed
+               normalizedMap := make(map[string]*types.ColumnImage, 
len(coumnMap))
+               for k, v := range coumnMap {
+                       normalizedMap[util.DelEscape(k, types.DBTypeMySQL)] = v
+               }
                for _, pk := range pkNameList {
-                       col := coumnMap[pk]
+                       cleanPK := util.DelEscape(pk, types.DBTypeMySQL)
+                       col := normalizedMap[cleanPK]
                        if col != nil {
                                params = append(params, col.Value)
                        }
diff --git a/pkg/datasource/sql/undo/builder/mysql_insert_undo_log_builder.go 
b/pkg/datasource/sql/undo/builder/mysql_insert_undo_log_builder.go
index fbb58103..bb88654b 100644
--- a/pkg/datasource/sql/undo/builder/mysql_insert_undo_log_builder.go
+++ b/pkg/datasource/sql/undo/builder/mysql_insert_undo_log_builder.go
@@ -27,7 +27,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo/executor"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
        "seata.apache.org/seata-go/v2/pkg/util/log"
 )
 
@@ -211,7 +211,7 @@ func (u *MySQLInsertUndoLogBuilder) containsPK(meta 
types.TableMeta, parseCtx *t
 
 // containPK compare column name and primary key name
 func (u *MySQLInsertUndoLogBuilder) containPK(columnName string, meta 
types.TableMeta) bool {
-       newColumnName := executor.DelEscape(columnName, types.DBTypeMySQL)
+       newColumnName := util.DelEscape(columnName, types.DBTypeMySQL)
        pkColumnNameList := meta.GetPrimaryKeyOnlyName()
        if len(pkColumnNameList) == 0 {
                return false
@@ -254,7 +254,7 @@ func (u *MySQLInsertUndoLogBuilder) getPkIndex(InsertStmt 
*ast.InsertStmt, meta
                tmpColumnMeta := columnMeta
                pkIndex++
                if u.containPK(tmpColumnMeta.ColumnName, meta) {
-                       pkIndexMap[executor.DelEscape(tmpColumnMeta.ColumnName, 
types.DBTypeMySQL)] = pkIndex
+                       pkIndexMap[util.DelEscape(tmpColumnMeta.ColumnName, 
types.DBTypeMySQL)] = pkIndex
                }
        }
 
diff --git 
a/pkg/datasource/sql/undo/builder/mysql_insertonduplicate_update_undo_log_builder.go
 
b/pkg/datasource/sql/undo/builder/mysql_insertonduplicate_update_undo_log_builder.go
index 75955eb6..58f43275 100644
--- 
a/pkg/datasource/sql/undo/builder/mysql_insertonduplicate_update_undo_log_builder.go
+++ 
b/pkg/datasource/sql/undo/builder/mysql_insertonduplicate_update_undo_log_builder.go
@@ -27,7 +27,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
-       "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo/executor"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
        "seata.apache.org/seata-go/v2/pkg/util/log"
 )
 
@@ -316,7 +316,7 @@ func (u *MySQLInsertOnDuplicateUndoLogBuilder) 
buildImageParameters(insert *ast.
                        return nil, fmt.Errorf("insert row's column size not 
equal to insert column size")
                }
                for i, col := range insertColumns {
-                       columnName := strings.ToLower(executor.DelEscape(col, 
types.DBTypeMySQL))
+                       columnName := strings.ToLower(util.DelEscape(col, 
types.DBTypeMySQL))
                        val := row[i]
                        if str, ok := val.(string); ok && 
strings.EqualFold(str, SqlPlaceholder) {
                                if placeHolderIndex >= len(args) {
diff --git a/pkg/datasource/sql/undo/executor/executor.go 
b/pkg/datasource/sql/undo/executor/executor.go
index a4246323..ae4b9912 100644
--- a/pkg/datasource/sql/undo/executor/executor.go
+++ b/pkg/datasource/sql/undo/executor/executor.go
@@ -29,6 +29,7 @@ import (
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/datasource"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
        serr "seata.apache.org/seata-go/v2/pkg/util/errors"
        "seata.apache.org/seata-go/v2/pkg/util/log"
 )
@@ -181,7 +182,8 @@ func (b *BaseExecutor) parsePkValues(rows []types.RowImage, 
pkNameList []string)
 
        for _, row := range rows {
                for _, column := range row.Columns {
-                       columnNameLower := strings.ToLower(column.ColumnName)
+                       cleanName := util.DelEscape(column.ColumnName, 
types.DBTypeMySQL)
+                       columnNameLower := strings.ToLower(cleanName)
                        if originalPk, exists := pkLookup[columnNameLower]; 
exists {
                                if pkValues[originalPk] == nil {
                                        pkValues[originalPk] = 
make([]types.ColumnImage, 0, len(rows))
diff --git a/pkg/datasource/sql/undo/executor/executor_test.go 
b/pkg/datasource/sql/undo/executor/executor_test.go
index eaaf6b37..5b2a2da9 100644
--- a/pkg/datasource/sql/undo/executor/executor_test.go
+++ b/pkg/datasource/sql/undo/executor/executor_test.go
@@ -609,3 +609,52 @@ func TestParsePkValuesNoMatchingPK(t *testing.T) {
        assert.NotNil(t, result)
        assert.Len(t, result, 0)
 }
+
+func TestParsePkValuesEscapedColumnName(t *testing.T) {
+       executor := &BaseExecutor{}
+
+       rows := []types.RowImage{
+               {Columns: []types.ColumnImage{
+                       {ColumnName: "`id`", Value: 1},
+                       {ColumnName: "`name`", Value: "test"},
+               }},
+               {Columns: []types.ColumnImage{
+                       {ColumnName: "`id`", Value: 2},
+                       {ColumnName: "`name`", Value: "test2"},
+               }},
+       }
+
+       pkNameList := []string{"id"}
+
+       result := executor.parsePkValues(rows, pkNameList)
+
+       assert.NotNil(t, result)
+       assert.Len(t, result, 1)
+       assert.Contains(t, result, "id")
+       assert.Len(t, result["id"], 2)
+       assert.Equal(t, 1, result["id"][0].Value)
+       assert.Equal(t, 2, result["id"][1].Value)
+}
+
+func TestParsePkValuesEscapedCompositePK(t *testing.T) {
+       executor := &BaseExecutor{}
+
+       rows := []types.RowImage{
+               {Columns: []types.ColumnImage{
+                       {ColumnName: "`order_id`", Value: 100},
+                       {ColumnName: "`user_id`", Value: 1},
+                       {ColumnName: "`amount`", Value: 99.99},
+               }},
+       }
+
+       pkNameList := []string{"order_id", "user_id"}
+
+       result := executor.parsePkValues(rows, pkNameList)
+
+       assert.NotNil(t, result)
+       assert.Len(t, result, 2)
+       assert.Contains(t, result, "order_id")
+       assert.Contains(t, result, "user_id")
+       assert.Equal(t, 100, result["order_id"][0].Value)
+       assert.Equal(t, 1, result["user_id"][0].Value)
+}
diff --git a/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor.go
index 85a01d5f..011da4c0 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor.go
@@ -25,6 +25,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 type mySQLUndoDeleteExecutor struct {
@@ -53,7 +54,7 @@ func (m *mySQLUndoDeleteExecutor) ExecuteOn(ctx 
context.Context, dbType types.DB
 
        for _, row := range beforeImage.Rows {
                undoValues := make([]interface{}, 0)
-               pkList, err := GetOrderedPkList(beforeImage, row, dbType)
+               pkList, err := util.GetOrderedPkList(beforeImage, row, dbType)
                if err != nil {
                        return err
                }
@@ -85,7 +86,7 @@ func (m *mySQLUndoDeleteExecutor) buildUndoSQL(dbType 
types.DBType) (string, err
 
        row := rows[0]
        fields := row.NonPrimaryKeys(row.Columns)
-       pkList, err := GetOrderedPkList(beforeImage, row, dbType)
+       pkList, err := util.GetOrderedPkList(beforeImage, row, dbType)
        if err != nil {
                return "", err
        }
@@ -98,7 +99,7 @@ func (m *mySQLUndoDeleteExecutor) buildUndoSQL(dbType 
types.DBType) (string, err
        )
 
        for key := range fields {
-               insertColumnSlice = append(insertColumnSlice, 
AddEscape(fields[key].ColumnName, dbType))
+               insertColumnSlice = append(insertColumnSlice, 
util.AddEscape(fields[key].ColumnName, dbType))
                insertValueSlice = append(insertValueSlice, "?")
        }
 
diff --git 
a/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor_test.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor_test.go
index d4f095d2..065cb2c8 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor_test.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_delete_executor_test.go
@@ -29,6 +29,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 func TestNewMySQLUndoDeleteExecutor(t *testing.T) {
@@ -126,7 +127,7 @@ func TestMySQLUndoDeleteExecutor_BuildUndoSQL(t *testing.T) 
{
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.PrimaryKey.Number() {
@@ -138,7 +139,7 @@ func TestMySQLUndoDeleteExecutor_BuildUndoSQL(t *testing.T) 
{
                        defer patches.Reset()
 
                        // Mock AddEscape function
-                       patches.ApplyFunc(AddEscape, func(columnName string, 
dbType types.DBType) string {
+                       patches.ApplyFunc(util.AddEscape, func(columnName 
string, dbType types.DBType) string {
                                return "`" + columnName + "`"
                        })
 
@@ -215,7 +216,7 @@ func TestMySQLUndoDeleteExecutor_ExecuteOn(t *testing.T) {
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.PrimaryKey.Number() {
@@ -227,7 +228,7 @@ func TestMySQLUndoDeleteExecutor_ExecuteOn(t *testing.T) {
                        defer patches.Reset()
 
                        // Mock AddEscape function
-                       patches.ApplyFunc(AddEscape, func(columnName string, 
dbType types.DBType) string {
+                       patches.ApplyFunc(util.AddEscape, func(columnName 
string, dbType types.DBType) string {
                                return "`" + columnName + "`"
                        })
 
diff --git a/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor.go
index 92174268..0eea275c 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor.go
@@ -24,6 +24,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 type mySQLUndoInsertExecutor struct {
@@ -90,7 +91,7 @@ func (m *mySQLUndoInsertExecutor) generateDeleteSql(
        image *types.RecordImage, rows []types.RowImage,
        dbType types.DBType, sqlUndoLog undo.SQLUndoLog) (string, error) {
 
-       colImages, err := GetOrderedPkList(image, rows[0], dbType)
+       colImages, err := util.GetOrderedPkList(image, rows[0], dbType)
        if err != nil {
                return "", err
        }
@@ -100,7 +101,7 @@ func (m *mySQLUndoInsertExecutor) generateDeleteSql(
                pkList = append(pkList, colImages[key].ColumnName)
        }
 
-       whereSql := BuildWhereConditionByPKs(pkList, dbType)
+       whereSql := util.BuildWhereConditionByPKs(pkList, dbType)
 
        deleteSqlTemplate := "DELETE FROM %s WHERE %s "
        return fmt.Sprintf(deleteSqlTemplate, sqlUndoLog.TableName, whereSql), 
nil
diff --git 
a/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor_test.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor_test.go
index 934199d2..0f9be1ff 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor_test.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_insert_executor_test.go
@@ -29,6 +29,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 func TestNewMySQLUndoInsertExecutor(t *testing.T) {
@@ -129,7 +130,7 @@ func TestMySQLUndoInsertExecutor_BuildUndoSQL(t *testing.T) 
{
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.PrimaryKey.Number() {
@@ -141,7 +142,7 @@ func TestMySQLUndoInsertExecutor_BuildUndoSQL(t *testing.T) 
{
                        defer patches.Reset()
 
                        // Mock BuildWhereConditionByPKs function
-                       patches.ApplyFunc(BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
+                       patches.ApplyFunc(util.BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
                                if len(pkNameList) == 1 {
                                        return "`" + pkNameList[0] + "` = ?"
                                } else if len(pkNameList) == 2 {
@@ -201,7 +202,7 @@ func TestMySQLUndoInsertExecutor_GenerateDeleteSql(t 
*testing.T) {
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.PrimaryKey.Number() {
@@ -213,7 +214,7 @@ func TestMySQLUndoInsertExecutor_GenerateDeleteSql(t 
*testing.T) {
                        defer patches.Reset()
 
                        // Mock BuildWhereConditionByPKs function
-                       patches.ApplyFunc(BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
+                       patches.ApplyFunc(util.BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
                                return "`" + pkNameList[0] + "` = ?"
                        })
 
@@ -329,6 +330,25 @@ func TestMySQLUndoInsertExecutor_ExecuteOn(t *testing.T) {
                        })
                        defer patches.Reset()
 
+                       // Mock GetOrderedPkList function
+                       patches.ApplyFunc(util.GetOrderedPkList, func(image 
*types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                               var pkList []types.ColumnImage
+                               for _, col := range row.Columns {
+                                       if col.KeyType == 
types.PrimaryKey.Number() {
+                                               pkList = append(pkList, col)
+                                       }
+                               }
+                               return pkList, nil
+                       })
+
+                       // Mock BuildWhereConditionByPKs function
+                       patches.ApplyFunc(util.BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
+                               if len(pkNameList) == 0 {
+                                       return ""
+                               }
+                               return "`" + pkNameList[0] + "` = ?"
+                       })
+
                        executor := &mySQLUndoInsertExecutor{
                                BaseExecutor: &BaseExecutor{
                                        sqlUndoLog: undo.SQLUndoLog{
diff --git a/pkg/datasource/sql/undo/executor/mysql_undo_update_executor.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_update_executor.go
index acbd9d95..8b22983b 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_update_executor.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_update_executor.go
@@ -25,6 +25,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 type mySQLUndoUpdateExecutor struct {
@@ -59,7 +60,7 @@ func (m *mySQLUndoUpdateExecutor) ExecuteOn(ctx 
context.Context, dbType types.DB
        beforeImage := m.sqlUndoLog.BeforeImage
        for _, row := range beforeImage.Rows {
                undoValues := make([]interface{}, 0)
-               pkList, err := GetOrderedPkList(beforeImage, row, dbType)
+               pkList, err := util.GetOrderedPkList(beforeImage, row, dbType)
                if err != nil {
                        return err
                }
@@ -95,11 +96,11 @@ func (m *mySQLUndoUpdateExecutor) buildUndoSQL(dbType 
types.DBType) (string, err
 
        nonPkFields := row.NonPrimaryKeys(row.Columns)
        for key := range nonPkFields {
-               updateColumnSlice = append(updateColumnSlice, 
AddEscape(nonPkFields[key].ColumnName, dbType)+" = ? ")
+               updateColumnSlice = append(updateColumnSlice, 
util.AddEscape(nonPkFields[key].ColumnName, dbType)+" = ? ")
        }
 
        updateColumns = strings.Join(updateColumnSlice, ", ")
-       pkList, err := GetOrderedPkList(beforeImage, row, dbType)
+       pkList, err := util.GetOrderedPkList(beforeImage, row, dbType)
        if err != nil {
                return "", err
        }
@@ -108,7 +109,7 @@ func (m *mySQLUndoUpdateExecutor) buildUndoSQL(dbType 
types.DBType) (string, err
                pkNameList = append(pkNameList, pkList[key].ColumnName)
        }
 
-       whereSql := BuildWhereConditionByPKs(pkNameList, dbType)
+       whereSql := util.BuildWhereConditionByPKs(pkNameList, dbType)
 
        // UpdateSqlTemplate UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 in (?) 
pk2 in (?)
        updateSqlTemplate := "UPDATE %s SET %s WHERE %s "
diff --git 
a/pkg/datasource/sql/undo/executor/mysql_undo_update_executor_test.go 
b/pkg/datasource/sql/undo/executor/mysql_undo_update_executor_test.go
index 33a376bf..5c869636 100644
--- a/pkg/datasource/sql/undo/executor/mysql_undo_update_executor_test.go
+++ b/pkg/datasource/sql/undo/executor/mysql_undo_update_executor_test.go
@@ -29,6 +29,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/undo"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
 )
 
 func TestNewMySQLUndoUpdateExecutor(t *testing.T) {
@@ -120,7 +121,7 @@ func TestMySQLUndoUpdateExecutor_BuildUndoSQL(t *testing.T) 
{
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.IndexTypePrimaryKey {
@@ -132,12 +133,12 @@ func TestMySQLUndoUpdateExecutor_BuildUndoSQL(t 
*testing.T) {
                        defer patches.Reset()
 
                        // Mock AddEscape function
-                       patches.ApplyFunc(AddEscape, func(columnName string, 
dbType types.DBType) string {
+                       patches.ApplyFunc(util.AddEscape, func(columnName 
string, dbType types.DBType) string {
                                return "`" + columnName + "`"
                        })
 
                        // Mock BuildWhereConditionByPKs function
-                       patches.ApplyFunc(BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
+                       patches.ApplyFunc(util.BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
                                if len(pkNameList) == 1 {
                                        return "`" + pkNameList[0] + "` = ?"
                                } else if len(pkNameList) == 2 {
@@ -237,7 +238,7 @@ func TestMySQLUndoUpdateExecutor_ExecuteOn(t *testing.T) {
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                        // Mock GetOrderedPkList function
-                       patches := gomonkey.ApplyFunc(GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
+                       patches := gomonkey.ApplyFunc(util.GetOrderedPkList, 
func(image *types.RecordImage, row types.RowImage, dbType types.DBType) 
([]types.ColumnImage, error) {
                                var pkList []types.ColumnImage
                                for _, col := range row.Columns {
                                        if col.KeyType == 
types.IndexTypePrimaryKey {
@@ -249,12 +250,12 @@ func TestMySQLUndoUpdateExecutor_ExecuteOn(t *testing.T) {
                        defer patches.Reset()
 
                        // Mock AddEscape function
-                       patches.ApplyFunc(AddEscape, func(columnName string, 
dbType types.DBType) string {
+                       patches.ApplyFunc(util.AddEscape, func(columnName 
string, dbType types.DBType) string {
                                return "`" + columnName + "`"
                        })
 
                        // Mock BuildWhereConditionByPKs function
-                       patches.ApplyFunc(BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
+                       patches.ApplyFunc(util.BuildWhereConditionByPKs, 
func(pkNameList []string, dbType types.DBType) string {
                                if len(pkNameList) == 1 {
                                        return "`" + pkNameList[0] + "` = ?"
                                }
diff --git a/pkg/datasource/sql/undo/executor/utils.go 
b/pkg/datasource/sql/undo/executor/utils.go
index 7becdc05..b95dc83c 100644
--- a/pkg/datasource/sql/undo/executor/utils.go
+++ b/pkg/datasource/sql/undo/executor/utils.go
@@ -23,6 +23,7 @@ import (
 
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/datasource"
        "seata.apache.org/seata-go/v2/pkg/datasource/sql/types"
+       "seata.apache.org/seata-go/v2/pkg/datasource/sql/util"
        "seata.apache.org/seata-go/v2/pkg/util/log"
 )
 
@@ -74,8 +75,9 @@ func rowListToMap(rows []types.RowImage, primaryKeyList 
[]string) map[string]map
                var firstUnderline bool
 
                for _, column := range row.Columns {
+                       cleanName := util.DelEscape(column.ColumnName, 
types.DBTypeMySQL)
                        for i, key := range primaryKeyList {
-                               if column.ColumnName == key {
+                               if cleanName == key {
                                        if firstUnderline && i > 0 {
                                                rowKey += "_##$$_"
                                        }
@@ -84,7 +86,7 @@ func rowListToMap(rows []types.RowImage, primaryKeyList 
[]string) map[string]map
                                        firstUnderline = true
                                }
                        }
-                       fieldMap[strings.ToUpper(column.ColumnName)] = 
column.Value
+                       fieldMap[strings.ToUpper(cleanName)] = column.Value
                }
                rowMap[rowKey] = fieldMap
        }
@@ -152,8 +154,14 @@ func buildPKParams(rows []types.RowImage, pkNameList 
[]string) []interface{} {
        params := make([]interface{}, 0)
        for _, row := range rows {
                coumnMap := row.GetColumnMap()
+               // Build a normalized map with escaped characters removed
+               normalizedMap := make(map[string]*types.ColumnImage, 
len(coumnMap))
+               for k, v := range coumnMap {
+                       normalizedMap[util.DelEscape(k, types.DBTypeMySQL)] = v
+               }
                for _, pk := range pkNameList {
-                       col := coumnMap[pk]
+                       cleanPK := util.DelEscape(pk, types.DBTypeMySQL)
+                       col := normalizedMap[cleanPK]
                        if col != nil {
                                params = append(params, col.Value)
                        }
diff --git a/pkg/datasource/sql/undo/executor/utils_test.go 
b/pkg/datasource/sql/undo/executor/utils_test.go
index 06780c4c..98a038e6 100644
--- a/pkg/datasource/sql/undo/executor/utils_test.go
+++ b/pkg/datasource/sql/undo/executor/utils_test.go
@@ -516,3 +516,56 @@ func TestBuildPKParams(t *testing.T) {
                })
        }
 }
+
+func TestRowListToMap_EscapedColumnNames(t *testing.T) {
+       rows := []types.RowImage{
+               {
+                       Columns: []types.ColumnImage{
+                               {ColumnName: "`id`", Value: 1},
+                               {ColumnName: "`name`", Value: "test"},
+                       },
+               },
+               {
+                       Columns: []types.ColumnImage{
+                               {ColumnName: "`id`", Value: 2},
+                               {ColumnName: "`name`", Value: "test2"},
+                       },
+               },
+       }
+       primaryKeyList := []string{"id"}
+
+       result := rowListToMap(rows, primaryKeyList)
+
+       assert.Len(t, result, 2)
+       // Verify rows can be found by their PK values
+       row1, exists := result["1"]
+       assert.True(t, exists, "Row with PK=1 should exist")
+       if exists {
+               // After fix, fieldMap key uses cleaned (unescaped) uppercase 
column name
+               assert.Equal(t, 1, row1["ID"])
+               assert.Equal(t, "test", row1["NAME"])
+       }
+}
+
+func TestBuildPKParams_EscapedColumnNames(t *testing.T) {
+       rows := []types.RowImage{
+               {
+                       Columns: []types.ColumnImage{
+                               {ColumnName: "`id`", Value: 1},
+                               {ColumnName: "`name`", Value: "test"},
+                       },
+               },
+               {
+                       Columns: []types.ColumnImage{
+                               {ColumnName: "`id`", Value: 2},
+                               {ColumnName: "`name`", Value: "test2"},
+                       },
+               },
+       }
+       pkNameList := []string{"id"}
+
+       result := buildPKParams(rows, pkNameList)
+
+       assert.Len(t, result, 2)
+       assert.Equal(t, []interface{}{1, 2}, result)
+}
diff --git a/pkg/datasource/sql/undo/executor/sql.go 
b/pkg/datasource/sql/util/escape.go
similarity index 96%
rename from pkg/datasource/sql/undo/executor/sql.go
rename to pkg/datasource/sql/util/escape.go
index 5c7e540a..1970477a 100644
--- a/pkg/datasource/sql/undo/executor/sql.go
+++ b/pkg/datasource/sql/util/escape.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package executor
+package util
 
 import (
        "database/sql"
@@ -92,7 +92,7 @@ func addEscape(colName string, dbType types.DBType, escape 
string) string {
                return colName
        }
 
-       if !checkEscape(colName, dbType) {
+       if !CheckEscape(colName, dbType) {
                return colName
        }
 
@@ -149,8 +149,8 @@ func addEscape(colName string, dbType types.DBType, escape 
string) string {
        return string(buf)
 }
 
-// checkEscape check whether given field or table name use keywords. the 
method has database special logic.
-func checkEscape(colName string, dbType types.DBType) bool {
+// CheckEscape check whether given field or table name use keywords. the 
method has database special logic.
+func CheckEscape(colName string, dbType types.DBType) bool {
        switch dbType {
        case types.DBTypeMySQL:
                if _, ok := types.GetMysqlKeyWord()[strings.ToUpper(colName)]; 
ok {
diff --git a/pkg/datasource/sql/undo/executor/sql_test.go 
b/pkg/datasource/sql/util/escape_test.go
similarity index 95%
rename from pkg/datasource/sql/undo/executor/sql_test.go
rename to pkg/datasource/sql/util/escape_test.go
index fa1b4a5b..4571f871 100644
--- a/pkg/datasource/sql/undo/executor/sql_test.go
+++ b/pkg/datasource/sql/util/escape_test.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package executor
+package util
 
 import (
        "log"
@@ -139,31 +139,31 @@ func TestAddEscapeSQLServer(t *testing.T) {
 func TestCheckEscapeMySQLKeywords(t *testing.T) {
        keywords := []string{"SELECT", "INSERT", "UPDATE", "DELETE", "ALTER", 
"CREATE", "DROP", "TABLE"}
        for _, keyword := range keywords {
-               result := checkEscape(keyword, types.DBTypeMySQL)
+               result := CheckEscape(keyword, types.DBTypeMySQL)
                assert.True(t, result, "Expected %s to be a MySQL keyword", 
keyword)
 
                lowerKeyword := keyword
-               result = checkEscape(lowerKeyword, types.DBTypeMySQL)
+               result = CheckEscape(lowerKeyword, types.DBTypeMySQL)
                assert.True(t, result, "Expected %s to be a MySQL keyword 
(case-insensitive)", lowerKeyword)
        }
 }
 
 func TestCheckEscapeNonKeyword(t *testing.T) {
-       result := checkEscape("my_column", types.DBTypeMySQL)
+       result := CheckEscape("my_column", types.DBTypeMySQL)
        assert.False(t, result)
 
-       result = checkEscape("user_name", types.DBTypeMySQL)
+       result = CheckEscape("user_name", types.DBTypeMySQL)
        assert.False(t, result)
 }
 
 func TestCheckEscapeNonMySQL(t *testing.T) {
-       result := checkEscape("anything", types.DBTypePostgreSQL)
+       result := CheckEscape("anything", types.DBTypePostgreSQL)
        assert.True(t, result)
 
-       result = checkEscape("anything", types.DBTypeOracle)
+       result = CheckEscape("anything", types.DBTypeOracle)
        assert.True(t, result)
 
-       result = checkEscape("anything", types.DBTypeSQLServer)
+       result = CheckEscape("anything", types.DBTypeSQLServer)
        assert.True(t, result)
 }
 
diff --git a/pkg/datasource/sql/util/lockkey.go 
b/pkg/datasource/sql/util/lockkey.go
index 9ce60985..b7e35d27 100644
--- a/pkg/datasource/sql/util/lockkey.go
+++ b/pkg/datasource/sql/util/lockkey.go
@@ -47,7 +47,8 @@ func BuildLockKey(records *types.RecordImage, meta 
types.TableMeta) string {
        columns := make([]ColMapItem, 0, len(keys))
        if len(records.Rows) > 0 {
                for colIdx, column := range records.Rows[0].Columns {
-                       if pkIdx, ok := keyIndexMap[column.ColumnName]; ok {
+                       cleanName := DelEscape(column.ColumnName, 
types.DBTypeMySQL)
+                       if pkIdx, ok := keyIndexMap[cleanName]; ok {
                                columns = append(columns, ColMapItem{pkIndex: 
pkIdx, colIndex: colIdx})
                        }
                }
diff --git a/pkg/datasource/sql/util/lockkey_test.go 
b/pkg/datasource/sql/util/lockkey_test.go
index e09db49d..c1656307 100644
--- a/pkg/datasource/sql/util/lockkey_test.go
+++ b/pkg/datasource/sql/util/lockkey_test.go
@@ -541,3 +541,71 @@ func TestBuildLockKey_LargeNumberOfRows(t *testing.T) {
        assert.Contains(t, lockKey, "1,2,3")
        assert.Contains(t, lockKey, ",99,100")
 }
+
+func TestBuildLockKey_EscapedColumnNames(t *testing.T) {
+       tableMeta := types.TableMeta{
+               TableName: "t_order",
+               Indexs: map[string]types.IndexMeta{
+                       "PRIMARY": {
+                               IType: types.IndexTypePrimaryKey,
+                               Columns: []types.ColumnMeta{
+                                       {ColumnName: "id"},
+                               },
+                       },
+               },
+       }
+
+       recordImage := &types.RecordImage{
+               TableName: "t_order",
+               SQLType:   types.SQLTypeInsert,
+               Rows: []types.RowImage{
+                       {
+                               Columns: []types.ColumnImage{
+                                       {ColumnName: "`id`", Value: 1, KeyType: 
types.IndexTypePrimaryKey},
+                                       {ColumnName: "`name`", Value: "order1"},
+                               },
+                       },
+                       {
+                               Columns: []types.ColumnImage{
+                                       {ColumnName: "`id`", Value: 2, KeyType: 
types.IndexTypePrimaryKey},
+                                       {ColumnName: "`name`", Value: "order2"},
+                               },
+                       },
+               },
+       }
+
+       lockKey := BuildLockKey(recordImage, tableMeta)
+       assert.Equal(t, "T_ORDER:1,2", lockKey)
+}
+
+func TestBuildLockKey_EscapedCompositeKey(t *testing.T) {
+       tableMeta := types.TableMeta{
+               TableName: "t_order",
+               Indexs: map[string]types.IndexMeta{
+                       "PRIMARY": {
+                               IType: types.IndexTypePrimaryKey,
+                               Columns: []types.ColumnMeta{
+                                       {ColumnName: "order_id"},
+                                       {ColumnName: "user_id"},
+                               },
+                       },
+               },
+       }
+
+       recordImage := &types.RecordImage{
+               TableName: "t_order",
+               SQLType:   types.SQLTypeInsert,
+               Rows: []types.RowImage{
+                       {
+                               Columns: []types.ColumnImage{
+                                       {ColumnName: "`order_id`", Value: 100, 
KeyType: types.IndexTypePrimaryKey},
+                                       {ColumnName: "`user_id`", Value: 1, 
KeyType: types.IndexTypePrimaryKey},
+                                       {ColumnName: "`amount`", Value: 99.99},
+                               },
+                       },
+               },
+       }
+
+       lockKey := BuildLockKey(recordImage, tableMeta)
+       assert.Equal(t, "T_ORDER:100_1", lockKey)
+}


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

Reply via email to