Repository: alibaba/fescar (java)

Issue:
rollback sql fix
the samples rollback generate rollback sql:
UPDATE t_asset_assign SET desc = ?, status = ?, update_time = ?, update_user = ? WHERE id = ?;
reason is that the parameter ‘desc ’ is mysql keyword, suggest to add `` char on rollback sql parameter at AbstractUndoExecutor.buildUndoSQL()

Pull request:
fixes #459.Avoid the problem of keywords as field names and table names
<!-- Please make sure you have read and understood the contributing guidelines -->

### Ⅰ. Describe what this PR did
1.Avoid the problem of keywords as field names and table names
2.fixes #459.

### Ⅱ. Does this pull request fix one issue?
<!-- If that, add "fixes #xxx" below in the next line, for example, fixes #97. -->
fixes #459 


### Ⅲ. Why don't you add test cases (unit test/integration test)? 


### Ⅳ. Describe how to verify it


### Ⅴ. Special notes for reviews



Patch:
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordChecker.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordChecker.java
new file mode 100644
index 000000000..54be43907
--- /dev/null
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordChecker.java
@@ -0,0 +1,40 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.rm.datasource.undo;
+
+/**
+ * @author Wu
+ * @date 2019/3/5
+ * The interface Keyword checker
+ */
+public interface KeywordChecker {
+    /**
+     * check whether given field name and table name use keywords
+     *
+     * @param fieldOrTableName
+     * @return
+     */
+    boolean check(String fieldOrTableName);
+
+    /**
+     * check whether given field name and table name use keywords and,if so,will add "`" to the name.
+     *
+     * @param fieldOrTableName
+     * @return
+     */
+    String checkAndReplace(String fieldOrTableName);
+}
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordCheckerFactory.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordCheckerFactory.java
new file mode 100644
index 000000000..9efd3d008
--- /dev/null
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/KeywordCheckerFactory.java
@@ -0,0 +1,44 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.rm.datasource.undo;
+
+import com.alibaba.druid.util.JdbcConstants;
+import com.alibaba.fescar.common.exception.NotSupportYetException;
+import com.alibaba.fescar.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker;
+
+/**
+ * @author Wu
+ * @date 2019/3/5
+ * The Type keyword checker factory
+ */
+public class KeywordCheckerFactory {
+
+    /**
+     * get keyword checker
+     *
+     * @param dbType
+     * @return
+     */
+    public static KeywordChecker getKeywordChecker(String dbType) {
+        if (dbType.equals(JdbcConstants.MYSQL)) {
+            return MySQLKeywordChecker.getInstance();
+        } else {
+            throw new NotSupportYetException(dbType);
+        }
+
+    }
+}
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java
index 64b150ffb..1993bd188 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java
@@ -18,13 +18,17 @@ package com.alibaba.fescar.rm.datasource.undo.mysql;
 
 import java.util.List;
 
+import com.alibaba.druid.util.JdbcConstants;
 import com.alibaba.fescar.common.exception.ShouldNeverHappenException;
 import com.alibaba.fescar.rm.datasource.sql.struct.Field;
 import com.alibaba.fescar.rm.datasource.sql.struct.KeyType;
 import com.alibaba.fescar.rm.datasource.sql.struct.Row;
 import com.alibaba.fescar.rm.datasource.sql.struct.TableRecords;
 import com.alibaba.fescar.rm.datasource.undo.AbstractUndoExecutor;
+import com.alibaba.fescar.rm.datasource.undo.KeywordChecker;
+import com.alibaba.fescar.rm.datasource.undo.KeywordCheckerFactory;
 import com.alibaba.fescar.rm.datasource.undo.SQLUndoLog;
+import com.alibaba.fescar.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker;
 
 /**
  * The type My sql undo delete executor.
@@ -42,6 +46,7 @@ public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor {
 
     @Override
     protected String buildUndoSQL() {
+        KeywordChecker keywordChecker= KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
         TableRecords beforeImage = sqlUndoLog.getBeforeImage();
         List<Row> beforeImageRows = beforeImage.getRows();
         if (beforeImageRows == null || beforeImageRows.size() == 0) {
@@ -64,7 +69,7 @@ public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor {
                     insertColumns.append(", ");
                     insertValues.append(", ");
                 }
-                insertColumns.append(field.getName());
+                insertColumns.append(keywordChecker.checkAndReplace(field.getName()));
                 insertValues.append("?");
             }
 
@@ -75,10 +80,10 @@ public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor {
             insertColumns.append(", ");
             insertValues.append(", ");
         }
-        insertColumns.append(pkField.getName());
+        insertColumns.append(keywordChecker.checkAndReplace(pkField.getName()));
         insertValues.append("?");
 
-        return "INSERT INTO " + sqlUndoLog.getTableName() + "(" + insertColumns.toString() + ") VALUES (" + insertValues.toString() + ")";
+        return "INSERT INTO " + keywordChecker.checkAndReplace(sqlUndoLog.getTableName()) + "(" + insertColumns.toString() + ") VALUES (" + insertValues.toString() + ")";
     }
 
     @Override
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java
index c032bc04c..0f097edb8 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java
@@ -21,13 +21,17 @@ import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.alibaba.druid.util.JdbcConstants;
 import com.alibaba.fescar.common.exception.ShouldNeverHappenException;
 import com.alibaba.fescar.rm.datasource.sql.struct.Field;
 import com.alibaba.fescar.rm.datasource.sql.struct.KeyType;
 import com.alibaba.fescar.rm.datasource.sql.struct.Row;
 import com.alibaba.fescar.rm.datasource.sql.struct.TableRecords;
 import com.alibaba.fescar.rm.datasource.undo.AbstractUndoExecutor;
+import com.alibaba.fescar.rm.datasource.undo.KeywordChecker;
+import com.alibaba.fescar.rm.datasource.undo.KeywordCheckerFactory;
 import com.alibaba.fescar.rm.datasource.undo.SQLUndoLog;
+import com.alibaba.fescar.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker;
 
 /**
  * The type My sql undo insert executor.
@@ -36,18 +40,19 @@ public class MySQLUndoInsertExecutor extends AbstractUndoExecutor {
 
     @Override
     protected String buildUndoSQL() {
+        KeywordChecker keywordChecker= KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
         TableRecords afterImage = sqlUndoLog.getAfterImage();
         List<Row> afterImageRows = afterImage.getRows();
         if (afterImageRows == null || afterImageRows.size() == 0) {
             throw new ShouldNeverHappenException("Invalid UNDO LOG");
         }
         Row row = afterImageRows.get(0);
-        StringBuffer mainSQL = new StringBuffer("DELETE FROM " + sqlUndoLog.getTableName());
+        StringBuffer mainSQL = new StringBuffer("DELETE FROM " + keywordChecker.checkAndReplace(sqlUndoLog.getTableName()));
         StringBuffer where = new StringBuffer(" WHERE ");
         boolean first = true;
         for (Field field : row.getFields()) {
             if (field.getKeyType() == KeyType.PrimaryKey) {
-                where.append(field.getName() + " = ? ");
+                where.append(keywordChecker.checkAndReplace(field.getName()) +" = ?");
             }
 
         }
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java
index 729f17478..319d54c68 100644
--- a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java
@@ -18,13 +18,17 @@ package com.alibaba.fescar.rm.datasource.undo.mysql;
 
 import java.util.List;
 
+import com.alibaba.druid.util.JdbcConstants;
 import com.alibaba.fescar.common.exception.ShouldNeverHappenException;
 import com.alibaba.fescar.rm.datasource.sql.struct.Field;
 import com.alibaba.fescar.rm.datasource.sql.struct.KeyType;
 import com.alibaba.fescar.rm.datasource.sql.struct.Row;
 import com.alibaba.fescar.rm.datasource.sql.struct.TableRecords;
 import com.alibaba.fescar.rm.datasource.undo.AbstractUndoExecutor;
+import com.alibaba.fescar.rm.datasource.undo.KeywordChecker;
+import com.alibaba.fescar.rm.datasource.undo.KeywordCheckerFactory;
 import com.alibaba.fescar.rm.datasource.undo.SQLUndoLog;
+import com.alibaba.fescar.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker;
 
 /**
  * The type My sql undo update executor.
@@ -33,25 +37,26 @@ public class MySQLUndoUpdateExecutor extends AbstractUndoExecutor {
 
     @Override
     protected String buildUndoSQL() {
+        KeywordChecker keywordChecker= KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
         TableRecords beforeImage = sqlUndoLog.getBeforeImage();
         List<Row> beforeImageRows = beforeImage.getRows();
         if (beforeImageRows == null || beforeImageRows.size() == 0) {
             throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO
         }
         Row row = beforeImageRows.get(0);
-        StringBuffer mainSQL = new StringBuffer("UPDATE " + sqlUndoLog.getTableName() + " SET ");
+        StringBuffer mainSQL = new StringBuffer("UPDATE " + keywordChecker.checkAndReplace(sqlUndoLog.getTableName()) + " SET ");
         StringBuffer where = new StringBuffer(" WHERE ");
         boolean first = true;
         for (Field field : row.getFields()) {
             if (field.getKeyType() == KeyType.PrimaryKey) {
-                where.append(field.getName() + " = ?");
+                where.append(keywordChecker.checkAndReplace(field.getName()) +" = ?");
             } else {
                 if (first) {
                     first = false;
                 } else {
                     mainSQL.append(", ");
                 }
-                mainSQL.append(field.getName() + " = ?");
+                mainSQL.append(keywordChecker.checkAndReplace(field.getName()) +" = ?");
             }
 
         }
diff --git a/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java
new file mode 100644
index 000000000..568a8cb71
--- /dev/null
+++ b/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java
@@ -0,0 +1,211 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.rm.datasource.undo.mysql.keyword;
+
+import com.alibaba.fescar.rm.datasource.undo.KeywordChecker;
+import org.apache.commons.lang3.StringUtils;
+
+
+/**
+ * @author Wu
+ * @date 2019/3/5
+ * MySQL keyword checker
+ */
+public class MySQLKeywordChecker implements KeywordChecker {
+    private static volatile KeywordChecker keywordChecker;
+
+    private MySQLKeywordChecker(){}
+
+    /**
+     * get instance of type MySQL keyword checker
+     * @return
+     */
+    public static KeywordChecker getInstance(){
+        if(keywordChecker==null){
+            synchronized (MySQLKeywordChecker.class){
+                if(keywordChecker==null){
+                    keywordChecker=new MySQLKeywordChecker();
+                }
+            }
+        }
+        return keywordChecker;
+    }
+    /**
+     * MySQL keyword
+     */
+    private enum MySQLKeyword {
+        SELECT("SELECT"),
+        DELETE("DELETE"),
+        INSERT("INSERT"),
+        UPDATE("UPDATE"),
+
+        FROM("FROM"),
+        HAVING("HAVING"),
+        WHERE("WHERE"),
+        ORDER("ORDER"),
+        BY("BY"),
+        GROUP("GROUP"),
+        INTO("INTO"),
+        AS("AS"),
+
+        CREATE("CREATE"),
+        ALTER("ALTER"),
+        DROP("DROP"),
+        SET("SET"),
+
+        NULL("NULL"),
+        NOT("NOT"),
+        DISTINCT("DISTINCT"),
+
+        TABLE("TABLE"),
+        TABLESPACE("TABLESPACE"),
+        VIEW("VIEW"),
+        SEQUENCE("SEQUENCE"),
+        TRIGGER("TRIGGER"),
+        USER("USER"),
+        INDEX("INDEX"),
+        SESSION("SESSION"),
+        PROCEDURE("PROCEDURE"),
+        FUNCTION("FUNCTION"),
+
+        PRIMARY("PRIMARY"),
+        KEY("KEY"),
+        DEFAULT("DEFAULT"),
+        CONSTRAINT("CONSTRAINT"),
+        CHECK("CHECK"),
+        UNIQUE("UNIQUE"),
+        FOREIGN("FOREIGN"),
+        REFERENCES("REFERENCES"),
+
+        EXPLAIN("EXPLAIN"),
+        FOR("FOR"),
+        IF("IF"),
+        SORT("SORT"),
+
+
+        ALL("ALL"),
+        UNION("UNION"),
+        EXCEPT("EXCEPT"),
+        INTERSECT("INTERSECT"),
+        MINUS("MINUS"),
+        INNER("INNER"),
+        LEFT("LEFT"),
+        RIGHT("RIGHT"),
+        FULL("FULL"),
+        OUTER("OUTER"),
+        JOIN("JOIN"),
+        ON("ON"),
+        SCHEMA("SCHEMA"),
+        CAST("CAST"),
+        COLUMN("COLUMN"),
+        USE("USE"),
+        DATABASE("DATABASE"),
+        TO("TO"),
+
+        AND("AND"),
+        OR("OR"),
+        XOR("XOR"),
+        CASE("CASE"),
+        WHEN("WHEN"),
+        THEN("THEN"),
+        ELSE("ELSE"),
+        ELSIF("ELSIF"),
+        END("END"),
+        EXISTS("EXISTS"),
+        IN("IN"),
+        CONTAINS("CONTAINS"),
+        RLIKE("RLIKE"),
+        FULLTEXT("FULLTEXT"),
+
+        NEW("NEW"),
+        ASC("ASC"),
+        DESC("DESC"),
+        IS("IS"),
+        LIKE("LIKE"),
+        ESCAPE("ESCAPE"),
+        BETWEEN("BETWEEN"),
+        VALUES("VALUES"),
+        INTERVAL("INTERVAL"),
+
+        LOCK("LOCK"),
+        SOME("SOME"),
+        ANY("ANY"),
+        TRUNCATE("TRUNCATE"),
+
+        RETURN("RETURN"),
+
+        // mysql
+        TRUE("TRUE"),
+        FALSE("FALSE"),
+        LIMIT("LIMIT"),
+        KILL("KILL"),
+        IDENTIFIED("IDENTIFIED"),
+        PASSWORD("PASSWORD"),
+        ALGORITHM("ALGORITHM"),
+        DUAL("DUAL"),
+        BINARY("BINARY"),
+        SHOW("SHOW"),
+        REPLACE("REPLACE"),
+
+
+        // MySql procedure add by zz
+        WHILE("WHILE"),
+        DO("DO"),
+        LEAVE("LEAVE"),
+        ITERATE("ITERATE"),
+        REPEAT("REPEAT"),
+        UNTIL("UNTIL"),
+        OPEN("OPEN"),
+        CLOSE("CLOSE"),
+        OUT("OUT"),
+        INOUT("INOUT"),
+        EXIT("EXIT"),
+        UNDO("UNDO"),
+        SQLSTATE("SQLSTATE"),
+        CONDITION("CONDITION"),
+        DIV("DIV");
+
+        public final String name;
+
+        MySQLKeyword() {
+            this(null);
+        }
+
+        MySQLKeyword(String name) {
+            this.name = name;
+        }
+    }
+
+
+    @Override
+    public boolean check(String fieldOrTableName) {
+        try {
+            if (StringUtils.isNotBlank(fieldOrTableName)) {
+                MySQLKeyword.valueOf(fieldOrTableName.toUpperCase());
+                return true;
+            }
+        } catch (IllegalArgumentException e) {
+            //do nothing
+        }
+        return false;
+    }
+
+    @Override
+    public String checkAndReplace(String fieldOrTableName) {
+        return check(fieldOrTableName)?"`" + fieldOrTableName + "`":fieldOrTableName;
+    }
+}


Test patch:
diff --git a/common/src/test/java/com/alibaba/fescar/common/XIDTest.java b/common/src/test/java/com/alibaba/fescar/common/XIDTest.java
index d987f9cf1..e6f71c56b 100644
--- a/common/src/test/java/com/alibaba/fescar/common/XIDTest.java
+++ b/common/src/test/java/com/alibaba/fescar/common/XIDTest.java
@@ -23,8 +23,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 
 /**
- * @author melon.zhao
- * @since 2019/2/22
+ * @author Otis.z
+ * @date 2019/2/22
  */
 public class XIDTest {
 
diff --git a/common/src/test/java/com/alibaba/fescar/common/loader/ChineseHello.java b/common/src/test/java/com/alibaba/fescar/common/loader/ChineseHello.java
new file mode 100644
index 000000000..42aa2f729
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/loader/ChineseHello.java
@@ -0,0 +1,30 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.loader;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+@LoadLevel(name = "ChineseHello",order = Integer.MIN_VALUE)
+public class ChineseHello implements Hello{
+
+    @Override
+    public String say() {
+        return "ni hao!";
+    }
+}
diff --git a/common/src/test/java/com/alibaba/fescar/common/loader/EnglishHello.java b/common/src/test/java/com/alibaba/fescar/common/loader/EnglishHello.java
new file mode 100644
index 000000000..fe3459025
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/loader/EnglishHello.java
@@ -0,0 +1,30 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.loader;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+@LoadLevel(name = "EnglishHello",order = 1)
+public class EnglishHello implements Hello {
+
+    @Override
+    public String say() {
+        return "hello!";
+    }
+}
diff --git a/common/src/test/java/com/alibaba/fescar/common/loader/EnhancedServiceLoaderTest.java b/common/src/test/java/com/alibaba/fescar/common/loader/EnhancedServiceLoaderTest.java
new file mode 100644
index 000000000..1deecdf60
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/loader/EnhancedServiceLoaderTest.java
@@ -0,0 +1,79 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.loader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+public class EnhancedServiceLoaderTest {
+
+    @Test
+    public void testLoadByClassAndClassLoader() {
+        Hello load = EnhancedServiceLoader.load(Hello.class, Hello.class.getClassLoader());
+        Assert.assertEquals(load.say(), "Bonjour");
+    }
+
+    @Test(expected = EnhancedServiceNotFoundException.class)
+    public void testLoadException() {
+        EnhancedServiceLoaderTest load = EnhancedServiceLoader.load(EnhancedServiceLoaderTest.class);
+    }
+
+
+    @Test
+    public void testLoadByClass() {
+        Hello load = EnhancedServiceLoader.load(Hello.class);
+        assertThat(load.say()).isEqualTo("Bonjour");
+    }
+
+    @Test
+    public void testLoadByClassAndActivateName() {
+        Hello englishHello = EnhancedServiceLoader.load(Hello.class, "EnglishHello");
+        assertThat(englishHello.say()).isEqualTo("hello!");
+    }
+
+    @Test
+    public void testLoadByClassAndClassLoaderAndActivateName() {
+        Hello englishHello = EnhancedServiceLoader
+            .load(Hello.class, "EnglishHello", EnhancedServiceLoaderTest.class.getClassLoader());
+        assertThat(englishHello.say()).isEqualTo("hello!");
+    }
+
+    @Test
+    public void getAllExtensionClass() {
+        List<Class> allExtensionClass = EnhancedServiceLoader.getAllExtensionClass(Hello.class);
+        assertThat(allExtensionClass.get(2).getSimpleName()).isEqualTo((FrenchHello.class.getSimpleName()));
+        assertThat(allExtensionClass.get(1).getSimpleName()).isEqualTo((EnglishHello.class.getSimpleName()));
+        assertThat(allExtensionClass.get(0).getSimpleName()).isEqualTo((ChineseHello.class.getSimpleName()));
+
+    }
+
+    @Test
+    public void getAllExtensionClass1() {
+        List<Class> allExtensionClass = EnhancedServiceLoader
+            .getAllExtensionClass(Hello.class, ClassLoader.getSystemClassLoader());
+        assertThat(allExtensionClass).isNotEmpty();
+    }
+
+
+}
\ No newline at end of file
diff --git a/common/src/test/java/com/alibaba/fescar/common/loader/FrenchHello.java b/common/src/test/java/com/alibaba/fescar/common/loader/FrenchHello.java
new file mode 100644
index 000000000..d3bedd38b
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/loader/FrenchHello.java
@@ -0,0 +1,30 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.loader;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+@LoadLevel(name = "FrenchHello",order = 2)
+public class FrenchHello  implements Hello{
+
+    @Override
+    public String say() {
+        return "Bonjour";
+    }
+}
diff --git a/common/src/test/java/com/alibaba/fescar/common/loader/Hello.java b/common/src/test/java/com/alibaba/fescar/common/loader/Hello.java
new file mode 100644
index 000000000..e3157b62e
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/loader/Hello.java
@@ -0,0 +1,25 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.loader;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+public interface Hello {
+    String say();
+}
diff --git a/common/src/test/java/com/alibaba/fescar/common/util/BlobUtilsTest.java b/common/src/test/java/com/alibaba/fescar/common/util/BlobUtilsTest.java
new file mode 100644
index 000000000..9f8c21e16
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/util/BlobUtilsTest.java
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.sql.SQLException;
+import javax.sql.rowset.serial.SerialBlob;
+import org.junit.Test;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+public class BlobUtilsTest {
+
+    @Test
+    public void testString2blob() throws SQLException {
+        assertThat(BlobUtils.string2blob("123abc")).isEqualTo(new SerialBlob("123abc".getBytes(Charset.forName("UTF-8"))));
+    }
+
+    @Test
+    public void testBlob2string() throws SQLException {
+        assertThat(BlobUtils.blob2string(new SerialBlob("123absent".getBytes(Charset.forName("UTF-8"))))).isEqualTo("123absent");
+    }
+
+    @Test
+    public void testInputStream2String() {
+        InputStream inputStream = BlobUtilsTest.class.getClassLoader().getResourceAsStream("test.txt");
+        assertThat(BlobUtils.inputStream2String(inputStream)).isEqualTo("abc\n"
+            + ":\"klsdf\n"
+            + "2ks,x:\".,-3sd˚ø≤ø¬≥");
+    }
+}
\ No newline at end of file
diff --git a/common/src/test/java/com/alibaba/fescar/common/util/NetUtilTest.java b/common/src/test/java/com/alibaba/fescar/common/util/NetUtilTest.java
new file mode 100644
index 000000000..4713fe9d4
--- /dev/null
+++ b/common/src/test/java/com/alibaba/fescar/common/util/NetUtilTest.java
@@ -0,0 +1,125 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.common.util;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import org.junit.Test;
+
+/**
+ * @author Otis.z
+ * @date 2019/2/26
+ */
+public class NetUtilTest {
+
+    private InetSocketAddress ipv4 = new InetSocketAddress(Inet4Address.getLocalHost().getHostName(), 3902);
+    private InetSocketAddress ipv6 = new InetSocketAddress(Inet6Address.getLocalHost().getHostName(), 3904);
+
+    public NetUtilTest() throws UnknownHostException {
+    }
+
+    @Test
+    public void testToStringAddress() {
+        try {
+            String stringAddress = NetUtil.toStringAddress(InetSocketAddress.createUnresolved("127.0.0.1", 9828));
+        } catch (Exception e) {
+            assertThat(e).isInstanceOf(NullPointerException.class);
+        }
+    }
+
+    @Test
+    public void testToStringAddress1() {
+        assertThat(NetUtil.toStringAddress((SocketAddress) ipv4))
+            .isEqualTo(ipv4.getAddress().getHostAddress() + ":" + ipv4.getPort());
+        assertThat(NetUtil.toStringAddress((SocketAddress) ipv6)).isEqualTo(
+            ipv6.getAddress().getHostAddress() + ":" + ipv6.getPort());
+    }
+
+    @Test
+    public void testToStringAddress2() {
+        assertThat(NetUtil.toStringAddress(ipv4)).isEqualTo(
+            ipv4.getAddress().getHostAddress() + ":" + ipv4.getPort());
+        assertThat(NetUtil.toStringAddress(ipv6)).isEqualTo(
+            ipv6.getAddress().getHostAddress() + ":" + ipv6.getPort());
+    }
+
+    @Test
+    public void testToIpAddress() throws UnknownHostException {
+        assertThat(NetUtil.toIpAddress(ipv4)).isEqualTo(ipv4.getAddress().getHostAddress());
+        assertThat(NetUtil.toIpAddress(ipv6)).isEqualTo(ipv6.getAddress().getHostAddress());
+    }
+
+
+    @Test
+    public void testToInetSocketAddress() {
+        try {
+            NetUtil.toInetSocketAddress("23939:ks");
+        } catch (Exception e) {
+            assertThat(e).isInstanceOf(NumberFormatException.class);
+        }
+    }
+
+    @Test
+    public void testToInetSocketAddress1() {
+        assertThat(NetUtil.toInetSocketAddress("kadfskl").getHostName()).isEqualTo("kadfskl");
+    }
+
+
+    @Test
+    public void testToLong() {
+        try {
+            NetUtil.toLong("kdskdsfk");
+        } catch (Exception e) {
+            assertThat(e).isInstanceOf(NullPointerException.class);
+        }
+    }
+
+    @Test
+    public void testToLong1() {
+        String[] split = "127.0.0.1".split("\\.");
+        long r = 0;
+        r = r | (Long.parseLong(split[0]) << 40);
+        r = r | (Long.parseLong(split[1]) << 32);
+        r = r | (Long.parseLong(split[2]) << 24);
+        r = r | (Long.parseLong(split[3]) << 16);
+        r = r | 0;
+        assertThat(NetUtil.toLong("127.0.0.1")).isEqualTo(r);
+
+    }
+
+
+    @Test
+    public void testGetLocalIp() {
+        assertThat(NetUtil.getLocalIp()).isNotNull();
+    }
+
+    @Test
+    public void testGetLocalHost() {
+        assertThat(NetUtil.getLocalHost()).isNotNull();
+    }
+
+    @Test
+    public void testGetLocalAddress() {
+        assertThat(NetUtil.getLocalAddress()).isNotNull();
+    }
+
+}
\ No newline at end of file
diff --git a/common/src/test/java/com/alibaba/fescar/common/StringUtilsTest.java b/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
similarity index 94%
rename from common/src/test/java/com/alibaba/fescar/common/StringUtilsTest.java
rename to common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
index 2a3d7cf03..515438e90 100644
--- a/common/src/test/java/com/alibaba/fescar/common/StringUtilsTest.java
+++ b/common/src/test/java/com/alibaba/fescar/common/util/StringUtilsTest.java
@@ -14,9 +14,8 @@
  *  limitations under the License.
  */
 
-package com.alibaba.fescar.common;
+package com.alibaba.fescar.common.util;
 
-import com.alibaba.fescar.common.util.StringUtils;
 import org.junit.Test;
 
 import javax.sql.rowset.serial.SerialBlob;
@@ -27,8 +26,8 @@ import java.sql.SQLException;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * @author melon.zhao
- * @since 2019/2/20
+ * @author Otis.z
+ * @date 2019/2/20
  */
 public class StringUtilsTest {
 
diff --git a/common/src/test/resources/META-INF/fescar/com.alibaba.fescar.common.loader.Hello b/common/src/test/resources/META-INF/fescar/com.alibaba.fescar.common.loader.Hello
new file mode 100644
index 000000000..450a1d361
--- /dev/null
+++ b/common/src/test/resources/META-INF/fescar/com.alibaba.fescar.common.loader.Hello
@@ -0,0 +1,18 @@
+#
+#  Copyright 1999-2018 Alibaba Group Holding Ltd.
+#
+#  Licensed 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.
+#
+
+com.alibaba.fescar.common.loader.FrenchHello
+com.alibaba.fescar.common.loader.EnglishHello
\ No newline at end of file
diff --git a/common/src/test/resources/META-INF/fescar/frenchhello/com.alibaba.fescar.common.loader.Hello b/common/src/test/resources/META-INF/fescar/frenchhello/com.alibaba.fescar.common.loader.Hello
new file mode 100644
index 000000000..f4b845a9e
--- /dev/null
+++ b/common/src/test/resources/META-INF/fescar/frenchhello/com.alibaba.fescar.common.loader.Hello
@@ -0,0 +1,18 @@
+#
+#  Copyright 1999-2018 Alibaba Group Holding Ltd.
+#
+#  Licensed 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.
+#
+
+
+com.alibaba.fescar.common.loader.EnglishHello
\ No newline at end of file
diff --git a/common/src/test/resources/META-INF/services/com.alibaba.fescar.common.loader.Hello b/common/src/test/resources/META-INF/services/com.alibaba.fescar.common.loader.Hello
new file mode 100644
index 000000000..5a0328a7e
--- /dev/null
+++ b/common/src/test/resources/META-INF/services/com.alibaba.fescar.common.loader.Hello
@@ -0,0 +1,19 @@
+#
+#
+#  Copyright 1999-2018 Alibaba Group Holding Ltd.
+#
+#  Licensed 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.
+#
+#
+
+com.alibaba.fescar.common.loader.ChineseHello
\ No newline at end of file
diff --git a/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java b/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java
new file mode 100644
index 000000000..94b8c76fe
--- /dev/null
+++ b/rm-datasource/src/test/java/com/alibaba/fescar/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java
@@ -0,0 +1,319 @@
+/*
+ *  Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ *  Licensed 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 com.alibaba.fescar.rm.datasource.undo.mysql.keyword;
+
+
+import com.alibaba.druid.util.JdbcConstants;
+import com.alibaba.fescar.rm.datasource.sql.SQLType;
+import com.alibaba.fescar.rm.datasource.sql.struct.Field;
+import com.alibaba.fescar.rm.datasource.sql.struct.KeyType;
+import com.alibaba.fescar.rm.datasource.sql.struct.Row;
+import com.alibaba.fescar.rm.datasource.sql.struct.TableRecords;
+import com.alibaba.fescar.rm.datasource.undo.*;
+import com.alibaba.fescar.rm.datasource.undo.mysql.MySQLUndoDeleteExecutor;
+import com.alibaba.fescar.rm.datasource.undo.mysql.MySQLUndoInsertExecutor;
+import com.alibaba.fescar.rm.datasource.undo.mysql.MySQLUndoUpdateExecutor;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.sql.*;
+
+
+/**
+ * @author Wu
+ * @date 2019/3/5
+ * The type MySQL keyword checker test.
+ */
+public class MySQLKeywordCheckerTest {
+
+    /**
+     * Test check
+     */
+    @Test
+    public void testCheck() {
+        KeywordChecker keywordChecker= KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
+        Assert.assertTrue(keywordChecker.check("desc"));
+
+
+    }
+
+    /**
+     * Test check and replace
+     */
+    @Test
+    public void testCheckAndReplace() {
+        KeywordChecker keywordChecker= KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
+        Assert.assertEquals("`desc`",keywordChecker.checkAndReplace("desc"));
+
+    }
+
+    /**
+     * Test keyword check with UPDATE case
+     */
+    @Test
+    public void testUpdateKeywordCheck() {
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setTableName("lock");
+        sqlUndoLog.setSqlType(SQLType.UPDATE);
+
+        TableRecords beforeImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        Row beforeRow = new Row();
+
+        Field pkField = new Field();
+        pkField.setKeyType(KeyType.PrimaryKey);
+        pkField.setName("id");
+        pkField.setType(Types.INTEGER);
+        pkField.setValue(213);
+        beforeRow.add(pkField);
+
+        Field name = new Field();
+        name.setName("desc");
+        name.setType(Types.VARCHAR);
+        name.setValue("FESCAR");
+        beforeRow.add(name);
+
+        Field nameOrder = new Field();
+        nameOrder.setName("order");
+        nameOrder.setType(Types.VARCHAR);
+        nameOrder.setValue("2");
+        beforeRow.add(nameOrder);
+
+        Field since = new Field();
+        since.setName("since");
+        since.setType(Types.VARCHAR);
+        since.setValue("2014");
+        beforeRow.add(since);
+
+        beforeImage.add(beforeRow);
+
+        TableRecords afterImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        Row afterRow = new Row();
+
+        Field pkField1 = new Field();
+        pkField1.setKeyType(KeyType.PrimaryKey);
+        pkField1.setName("id");
+        pkField1.setType(Types.INTEGER);
+        pkField1.setValue(213);
+        afterRow.add(pkField1);
+
+        Field name1 = new Field();
+        name1.setName("name");
+        name1.setType(Types.VARCHAR);
+        name1.setValue("GTS");
+        afterRow.add(name1);
+
+        Field since1 = new Field();
+        since1.setName("since");
+        since1.setType(Types.VARCHAR);
+        since1.setValue("2016");
+        afterRow.add(since1);
+
+        afterImage.add(afterRow);
+
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        MySQLUndoUpdateExecutorExtension mySQLUndoUpdateExecutor = new MySQLUndoUpdateExecutorExtension(sqlUndoLog);
+
+        Assert.assertEquals("UPDATE `lock` SET `desc` = ?, `order` = ?, since = ? WHERE id = ?", mySQLUndoUpdateExecutor.getSql());
+
+    }
+
+   private static class MySQLUndoUpdateExecutorExtension extends MySQLUndoUpdateExecutor{
+       /**
+        * Instantiates a new My sql undo update executor.
+        *
+        * @param sqlUndoLog the sql undo log
+        */
+       public MySQLUndoUpdateExecutorExtension(SQLUndoLog sqlUndoLog) {
+           super(sqlUndoLog);
+       }
+
+       public String getSql(){
+            return super.buildUndoSQL();
+        }
+    }
+
+    /**
+     * Test keyword check with INSERT case
+     */
+    @Test
+    public void testInsertKeywordCheck() {
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setTableName("lock");
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+
+        TableRecords beforeImage = TableRecords.empty(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        TableRecords afterImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        Row afterRow1 = new Row();
+
+        Field pkField = new Field();
+        pkField.setKeyType(KeyType.PrimaryKey);
+        pkField.setName("id");
+        pkField.setType(Types.INTEGER);
+        pkField.setValue(213);
+        afterRow1.add(pkField);
+
+        Field name = new Field();
+        name.setName("desc");
+        name.setType(Types.VARCHAR);
+        name.setValue("FESCAR");
+        afterRow1.add(name);
+
+        Field since = new Field();
+        since.setName("order");
+        since.setType(Types.VARCHAR);
+        since.setValue("2014");
+        afterRow1.add(since);
+
+        Row afterRow = new Row();
+
+        Field pkField1 = new Field();
+        pkField1.setKeyType(KeyType.PrimaryKey);
+        pkField1.setName("id");
+        pkField1.setType(Types.INTEGER);
+        pkField1.setValue(214);
+        afterRow.add(pkField1);
+
+        Field name1 = new Field();
+        name1.setName("name");
+        name1.setType(Types.VARCHAR);
+        name1.setValue("GTS");
+        afterRow.add(name1);
+
+        Field since1 = new Field();
+        since1.setName("since");
+        since1.setType(Types.VARCHAR);
+        since1.setValue("2016");
+        afterRow.add(since1);
+
+        afterImage.add(afterRow1);
+        afterImage.add(afterRow);
+
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        MySQLUndoInsertExecutorExtension mySQLUndoInsertExecutor = new MySQLUndoInsertExecutorExtension(sqlUndoLog);
+
+        Assert.assertEquals("DELETE FROM `lock` WHERE id = ?", mySQLUndoInsertExecutor.getSql());
+
+    }
+
+    private static class MySQLUndoInsertExecutorExtension extends MySQLUndoInsertExecutor {
+        /**
+         * Instantiates a new My sql undo insert executor.
+         *
+         * @param sqlUndoLog the sql undo log
+         */
+        public MySQLUndoInsertExecutorExtension(SQLUndoLog sqlUndoLog) {
+            super(sqlUndoLog);
+        }
+
+        public String getSql(){
+            return super.buildUndoSQL();
+        }
+    }
+
+    /**
+     * Test keyword check with DELETE case
+     */
+    @Test
+    public void testDeleteKeywordCheck() {
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setTableName("lock");
+        sqlUndoLog.setSqlType(SQLType.DELETE);
+
+        TableRecords afterImage = TableRecords.empty(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        TableRecords beforeImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "id"));
+
+        Row afterRow1 = new Row();
+
+        Field pkField = new Field();
+        pkField.setKeyType(KeyType.PrimaryKey);
+        pkField.setName("id");
+        pkField.setType(Types.INTEGER);
+        pkField.setValue(213);
+        afterRow1.add(pkField);
+
+        Field name = new Field();
+        name.setName("desc");
+        name.setType(Types.VARCHAR);
+        name.setValue("FESCAR");
+        afterRow1.add(name);
+
+        Field since = new Field();
+        since.setName("order");
+        since.setType(Types.VARCHAR);
+        since.setValue("2014");
+        afterRow1.add(since);
+
+        Row afterRow = new Row();
+
+        Field pkField1 = new Field();
+        pkField1.setKeyType(KeyType.PrimaryKey);
+        pkField1.setName("id");
+        pkField1.setType(Types.INTEGER);
+        pkField1.setValue(214);
+        afterRow.add(pkField1);
+
+        Field name1 = new Field();
+        name1.setName("name");
+        name1.setType(Types.VARCHAR);
+        name1.setValue("GTS");
+        afterRow.add(name1);
+
+        Field since1 = new Field();
+        since1.setName("since");
+        since1.setType(Types.VARCHAR);
+        since1.setValue("2016");
+        afterRow.add(since1);
+
+        beforeImage.add(afterRow1);
+        beforeImage.add(afterRow);
+
+        sqlUndoLog.setAfterImage(afterImage);
+        sqlUndoLog.setBeforeImage(beforeImage);
+
+        MySQLUndoDeleteExecutorExtension mySQLUndoDeleteExecutor = new MySQLUndoDeleteExecutorExtension(sqlUndoLog);
+
+        Assert.assertEquals("INSERT INTO `lock`(`desc`, `order`, id) VALUES (?, ?, ?)", mySQLUndoDeleteExecutor.getSql());
+
+    }
+
+    private static class MySQLUndoDeleteExecutorExtension extends MySQLUndoDeleteExecutor {
+        /**
+         * Instantiates a new My sql undo delete executor.
+         *
+         * @param sqlUndoLog the sql undo log
+         */
+        public MySQLUndoDeleteExecutorExtension(SQLUndoLog sqlUndoLog) {
+            super(sqlUndoLog);
+        }
+
+        public String getSql(){
+            return super.buildUndoSQL();
+        }
+    }
+
+
+
+}