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

jinrongtong pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git


The following commit(s) were added to refs/heads/develop by this push:
     new 867c29615 [ISSUE #6916] support startsWith and endsWith in sql filter. 
(#6915)
867c29615 is described below

commit 867c296150ec9734d10917dffe3fb453317e7fc2
Author: yuz10 <[email protected]>
AuthorDate: Fri Jun 23 15:41:16 2023 +0800

    [ISSUE #6916] support startsWith and endsWith in sql filter. (#6915)
    
    * support startsWith and endsWith in sql filter.
    
    * add tests
    
    * add tests
---
 .../filter/expression/ComparisonExpression.java    | 168 +++++++
 .../rocketmq/filter/parser/SelectorParser.java     | 554 ++++++++++++---------
 .../rocketmq/filter/parser/SelectorParser.jj       |  24 +
 .../filter/parser/SelectorParserConstants.java     |  20 +-
 .../filter/parser/SelectorParserTokenManager.java  | 230 ++++++---
 .../org/apache/rocketmq/filter/ExpressionTest.java | 216 ++++----
 6 files changed, 833 insertions(+), 379 deletions(-)

diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java
 
b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java
index ff9d84af0..14fd7045b 100644
--- 
a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java
+++ 
b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java
@@ -153,6 +153,174 @@ public abstract class ComparisonExpression extends 
BinaryExpression implements B
         return new NotContainsExpression(left, search);
     }
 
+    static class StartsWithExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public StartsWithExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "STARTSWITH";
+        }
+
+        public Object evaluate(EvaluationContext message) throws Exception {
+
+            if (search == null || search.length() == 0) {
+                return Boolean.FALSE;
+            }
+
+            Object rv = this.getRight().evaluate(message);
+
+            if (rv == null) {
+                return Boolean.FALSE;
+            }
+
+            if (!(rv instanceof String)) {
+                return Boolean.FALSE;
+            }
+
+            return ((String)rv).startsWith(search) ? Boolean.TRUE : 
Boolean.FALSE;
+        }
+
+        public boolean matches(EvaluationContext message) throws Exception {
+            Object object = evaluate(message);
+            return object != null && object == Boolean.TRUE;
+        }
+    }
+
+    static class NotStartsWithExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public NotStartsWithExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "NOT STARTSWITH";
+        }
+
+        public Object evaluate(EvaluationContext message) throws Exception {
+
+            if (search == null || search.length() == 0) {
+                return Boolean.FALSE;
+            }
+
+            Object rv = this.getRight().evaluate(message);
+
+            if (rv == null) {
+                return Boolean.FALSE;
+            }
+
+            if (!(rv instanceof String)) {
+                return Boolean.FALSE;
+            }
+
+            return ((String)rv).startsWith(search) ? Boolean.FALSE : 
Boolean.TRUE;
+        }
+
+        public boolean matches(EvaluationContext message) throws Exception {
+            Object object = evaluate(message);
+            return object != null && object == Boolean.TRUE;
+        }
+    }
+
+    public static BooleanExpression createStartsWith(Expression left, String 
search) {
+        return new StartsWithExpression(left, search);
+    }
+
+    public static BooleanExpression createNotStartsWith(Expression left, 
String search) {
+        return new NotStartsWithExpression(left, search);
+    }
+
+    static class EndsWithExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public EndsWithExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "ENDSWITH";
+        }
+
+        public Object evaluate(EvaluationContext message) throws Exception {
+
+            if (search == null || search.length() == 0) {
+                return Boolean.FALSE;
+            }
+
+            Object rv = this.getRight().evaluate(message);
+
+            if (rv == null) {
+                return Boolean.FALSE;
+            }
+
+            if (!(rv instanceof String)) {
+                return Boolean.FALSE;
+            }
+
+            return ((String)rv).endsWith(search) ? Boolean.TRUE : 
Boolean.FALSE;
+        }
+
+        public boolean matches(EvaluationContext message) throws Exception {
+            Object object = evaluate(message);
+            return object != null && object == Boolean.TRUE;
+        }
+    }
+
+    static class NotEndsWithExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public NotEndsWithExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "NOT ENDSWITH";
+        }
+
+        public Object evaluate(EvaluationContext message) throws Exception {
+
+            if (search == null || search.length() == 0) {
+                return Boolean.FALSE;
+            }
+
+            Object rv = this.getRight().evaluate(message);
+
+            if (rv == null) {
+                return Boolean.FALSE;
+            }
+
+            if (!(rv instanceof String)) {
+                return Boolean.FALSE;
+            }
+
+            return ((String)rv).endsWith(search) ? Boolean.FALSE : 
Boolean.TRUE;
+        }
+
+        public boolean matches(EvaluationContext message) throws Exception {
+            Object object = evaluate(message);
+            return object != null && object == Boolean.TRUE;
+        }
+    }
+
+    public static BooleanExpression createEndsWith(Expression left, String 
search) {
+        return new EndsWithExpression(left, search);
+    }
+
+    public static BooleanExpression createNotEndsWith(Expression left, String 
search) {
+        return new NotEndsWithExpression(left, search);
+    }
+
     @SuppressWarnings({"rawtypes", "unchecked"})
     public static BooleanExpression createInFilter(Expression left, List 
elements) {
 
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java
index d23e6ee97..0aaf2bc01 100644
--- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java
+++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java
@@ -173,21 +173,21 @@ public class SelectorParser implements 
SelectorParserConstants {
         while (true) {
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
                 case IS:
-                case 23:
-                case 24:
+                case 25:
+                case 26:
                     break;
                 default:
                     jjLa1[2] = jjGen;
                     break label_3;
             }
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 23:
-                    jj_consume_token(23);
+                case 25:
+                    jj_consume_token(25);
                     right = comparisonExpression();
                     left = ComparisonExpression.createEqual(left, right);
                     break;
-                case 24:
-                    jj_consume_token(24);
+                case 26:
+                    jj_consume_token(26);
                     right = comparisonExpression();
                     left = ComparisonExpression.createNotEqual(left, right);
                     break;
@@ -235,33 +235,35 @@ public class SelectorParser implements 
SelectorParserConstants {
                 case BETWEEN:
                 case IN:
                 case CONTAINS:
-                case 25:
-                case 26:
+                case STARTSWITH:
+                case ENDSWITH:
                 case 27:
                 case 28:
+                case 29:
+                case 30:
                     break;
                 default:
                     jjLa1[5] = jjGen;
                     break label_4;
             }
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 25:
-                    jj_consume_token(25);
+                case 27:
+                    jj_consume_token(27);
                     right = unaryExpr();
                     left = ComparisonExpression.createGreaterThan(left, right);
                     break;
-                case 26:
-                    jj_consume_token(26);
+                case 28:
+                    jj_consume_token(28);
                     right = unaryExpr();
                     left = ComparisonExpression.createGreaterThanEqual(left, 
right);
                     break;
-                case 27:
-                    jj_consume_token(27);
+                case 29:
+                    jj_consume_token(29);
                     right = unaryExpr();
                     left = ComparisonExpression.createLessThan(left, right);
                     break;
-                case 28:
-                    jj_consume_token(28);
+                case 30:
+                    jj_consume_token(30);
                     right = unaryExpr();
                     left = ComparisonExpression.createLessThanEqual(left, 
right);
                     break;
@@ -279,73 +281,105 @@ public class SelectorParser implements 
SelectorParserConstants {
                         left = ComparisonExpression.createNotContains(left, t);
                     } else {
                         switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                            case BETWEEN:
-                                jj_consume_token(BETWEEN);
-                                low = unaryExpr();
-                                jj_consume_token(AND);
-                                high = unaryExpr();
-                                left = 
ComparisonExpression.createBetween(left, low, high);
+                            case STARTSWITH:
+                                jj_consume_token(STARTSWITH);
+                                t = stringLitteral();
+                                left = 
ComparisonExpression.createStartsWith(left, t);
                                 break;
                             default:
                                 jjLa1[9] = jjGen;
                                 if (jj_2_3(2)) {
                                     jj_consume_token(NOT);
-                                    jj_consume_token(BETWEEN);
-                                    low = unaryExpr();
-                                    jj_consume_token(AND);
-                                    high = unaryExpr();
-                                    left = 
ComparisonExpression.createNotBetween(left, low, high);
+                                    jj_consume_token(STARTSWITH);
+                                    t = stringLitteral();
+                                    left = 
ComparisonExpression.createNotStartsWith(left, t);
                                 } else {
                                     switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                                        case IN:
-                                            jj_consume_token(IN);
-                                            jj_consume_token(29);
+                                        case ENDSWITH:
+                                            jj_consume_token(ENDSWITH);
                                             t = stringLitteral();
-                                            list = new ArrayList();
-                                            list.add(t);
-                                            label_5:
-                                            while (true) {
-                                                switch ((jjNtk == -1) ? 
jj_ntk() : jjNtk) {
-                                                    case 30:
-                                                        break;
-                                                    default:
-                                                        jjLa1[6] = jjGen;
-                                                        break label_5;
-                                                }
-                                                jj_consume_token(30);
-                                                t = stringLitteral();
-                                                list.add(t);
-                                            }
-                                            jj_consume_token(31);
-                                            left = 
ComparisonExpression.createInFilter(left, list);
+                                            left = 
ComparisonExpression.createEndsWith(left, t);
                                             break;
                                         default:
                                             jjLa1[10] = jjGen;
                                             if (jj_2_4(2)) {
                                                 jj_consume_token(NOT);
-                                                jj_consume_token(IN);
-                                                jj_consume_token(29);
+                                                jj_consume_token(ENDSWITH);
                                                 t = stringLitteral();
-                                                list = new ArrayList();
-                                                list.add(t);
-                                                label_6:
-                                                while (true) {
-                                                    switch ((jjNtk == -1) ? 
jj_ntk() : jjNtk) {
-                                                        case 30:
-                                                            break;
-                                                        default:
-                                                            jjLa1[7] = jjGen;
-                                                            break label_6;
-                                                    }
-                                                    jj_consume_token(30);
-                                                    t = stringLitteral();
-                                                    list.add(t);
-                                                }
-                                                jj_consume_token(31);
-                                                left = 
ComparisonExpression.createNotInFilter(left, list);
+                                                left = 
ComparisonExpression.createNotEndsWith(left, t);
                                             } else {
-                                                jj_consume_token(-1);
-                                                throw new ParseException();
+                                                switch ((jjNtk == -1) ? 
jj_ntk() : jjNtk) {
+                                                    case BETWEEN:
+                                                        
jj_consume_token(BETWEEN);
+                                                        low = unaryExpr();
+                                                        jj_consume_token(AND);
+                                                        high = unaryExpr();
+                                                        left = 
ComparisonExpression.createBetween(left, low, high);
+                                                        break;
+                                                    default:
+                                                        jjLa1[11] = jjGen;
+                                                        if (jj_2_5(2)) {
+                                                            
jj_consume_token(NOT);
+                                                            
jj_consume_token(BETWEEN);
+                                                            low = unaryExpr();
+                                                            
jj_consume_token(AND);
+                                                            high = unaryExpr();
+                                                            left = 
ComparisonExpression.createNotBetween(left, low, high);
+                                                        } else {
+                                                            switch ((jjNtk == 
-1) ? jj_ntk() : jjNtk) {
+                                                                case IN:
+                                                                    
jj_consume_token(IN);
+                                                                    
jj_consume_token(31);
+                                                                    t = 
stringLitteral();
+                                                                    list = new 
ArrayList();
+                                                                    
list.add(t);
+                                                                    label_5:
+                                                                    while 
(true) {
+                                                                        switch 
((jjNtk == -1) ? jj_ntk() : jjNtk) {
+                                                                            
case 32:
+                                                                               
 break;
+                                                                            
default:
+                                                                               
 jjLa1[6] = jjGen;
+                                                                               
 break label_5;
+                                                                        }
+                                                                        
jj_consume_token(32);
+                                                                        t = 
stringLitteral();
+                                                                        
list.add(t);
+                                                                    }
+                                                                    
jj_consume_token(33);
+                                                                    left = 
ComparisonExpression.createInFilter(left, list);
+                                                                    break;
+                                                                default:
+                                                                    jjLa1[12] 
= jjGen;
+                                                                    if 
(jj_2_6(2)) {
+                                                                        
jj_consume_token(NOT);
+                                                                        
jj_consume_token(IN);
+                                                                        
jj_consume_token(31);
+                                                                        t = 
stringLitteral();
+                                                                        list = 
new ArrayList();
+                                                                        
list.add(t);
+                                                                        
label_6:
+                                                                        while 
(true) {
+                                                                            
switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
+                                                                               
 case 32:
+                                                                               
     break;
+                                                                               
 default:
+                                                                               
     jjLa1[7] = jjGen;
+                                                                               
     break label_6;
+                                                                            }
+                                                                            
jj_consume_token(32);
+                                                                            t 
= stringLitteral();
+                                                                            
list.add(t);
+                                                                        }
+                                                                        
jj_consume_token(33);
+                                                                        left = 
ComparisonExpression.createNotInFilter(left, list);
+                                                                    } else {
+                                                                        
jj_consume_token(-1);
+                                                                        throw 
new ParseException();
+                                                                    }
+                                                            }
+                                                        }
+                                                }
                                             }
                                     }
                                 }
@@ -362,13 +396,13 @@ public class SelectorParser implements 
SelectorParserConstants {
     final public Expression unaryExpr() throws ParseException {
         String s = null;
         Expression left = null;
-        if (jj_2_5(2147483647)) {
-            jj_consume_token(32);
+        if (jj_2_7(2147483647)) {
+            jj_consume_token(34);
             left = unaryExpr();
         } else {
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 33:
-                    jj_consume_token(33);
+                case 35:
+                    jj_consume_token(35);
                     left = unaryExpr();
                     left = UnaryExpression.createNegate(left);
                     break;
@@ -384,11 +418,11 @@ public class SelectorParser implements 
SelectorParserConstants {
                 case FLOATING_POINT_LITERAL:
                 case STRING_LITERAL:
                 case ID:
-                case 29:
+                case 31:
                     left = primaryExpr();
                     break;
                 default:
-                    jjLa1[11] = jjGen;
+                    jjLa1[13] = jjGen;
                     jj_consume_token(-1);
                     throw new ParseException();
             }
@@ -413,13 +447,13 @@ public class SelectorParser implements 
SelectorParserConstants {
             case ID:
                 left = variable();
                 break;
-            case 29:
-                jj_consume_token(29);
-                left = orExpression();
+            case 31:
                 jj_consume_token(31);
+                left = orExpression();
+                jj_consume_token(33);
                 break;
             default:
-                jjLa1[12] = jjGen;
+                jjLa1[14] = jjGen;
                 jj_consume_token(-1);
                 throw new ParseException();
         }
@@ -459,7 +493,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                 left = BooleanConstantExpression.NULL;
                 break;
             default:
-                jjLa1[13] = jjGen;
+                jjLa1[15] = jjGen;
                 jj_consume_token(-1);
                 throw new ParseException();
         }
@@ -478,7 +512,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         String image = t.image;
         for (int i = 1; i < image.length() - 1; i++) {
             char c = image.charAt(i);
-            if (c == '\'')
+            if (c == '\u005c'')
                 i++;
             rc.append(c);
         }
@@ -559,17 +593,44 @@ public class SelectorParser implements 
SelectorParserConstants {
         }
     }
 
-    private boolean jj_3R_21() {
-        if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
-        return false;
+    private boolean jj_2_6(int xla) {
+        jjLa = xla;
+        jjLastpos = jjScanpos = token;
+        try {
+            return !jj_3_6();
+        } catch (LookaheadSuccess ls) {
+            return true;
+        } finally {
+            jj_save(5, xla);
+        }
+    }
+
+    private boolean jj_2_7(int xla) {
+        jjLa = xla;
+        jjLastpos = jjScanpos = token;
+        try {
+            return !jj_3_7();
+        } catch (LookaheadSuccess ls) {
+            return true;
+        } finally {
+            jj_save(6, xla);
+        }
     }
 
     private boolean jj_3R_34() {
-        if (jj_scan_token(24)) return true;
+        if (jj_scan_token(26)) return true;
         if (jj_3R_30()) return true;
         return false;
     }
 
+    private boolean jj_3R_43() {
+        if (jj_scan_token(BETWEEN)) return true;
+        if (jj_3R_7()) return true;
+        if (jj_scan_token(AND)) return true;
+        if (jj_3R_7()) return true;
+        return false;
+    }
+
     private boolean jj_3R_31() {
         Token xsp;
         xsp = jjScanpos;
@@ -587,67 +648,67 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private boolean jj_3R_33() {
-        if (jj_scan_token(23)) return true;
+        if (jj_scan_token(25)) return true;
         if (jj_3R_30()) return true;
         return false;
     }
 
-    private boolean jj_3R_20() {
-        if (jj_scan_token(DECIMAL_LITERAL)) return true;
+    private boolean jj_3_4() {
+        if (jj_scan_token(NOT)) return true;
+        if (jj_scan_token(ENDSWITH)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
-    private boolean jj_3R_42() {
-        if (jj_scan_token(IN)) return true;
-        if (jj_scan_token(29)) return true;
-        if (jj_3R_27()) return true;
-        Token xsp;
-        while (true) {
-            xsp = jjScanpos;
-            if (jj_3R_43()) {
-                jjScanpos = xsp;
-                break;
-            }
-        }
+    private boolean jj_3R_15() {
         if (jj_scan_token(31)) return true;
+        if (jj_3R_18()) return true;
+        if (jj_scan_token(33)) return true;
         return false;
     }
 
-    private boolean jj_3R_19() {
+    private boolean jj_3R_14() {
+        if (jj_3R_17()) return true;
+        return false;
+    }
+
+    private boolean jj_3R_13() {
+        if (jj_3R_16()) return true;
+        return false;
+    }
+
+    private boolean jj_3R_42() {
+        if (jj_scan_token(ENDSWITH)) return true;
         if (jj_3R_27()) return true;
         return false;
     }
 
-    private boolean jj_3R_28() {
-        if (jj_3R_30()) return true;
+    private boolean jj_3R_17() {
+        if (jj_scan_token(ID)) return true;
+        return false;
+    }
+
+    private boolean jj_3R_12() {
         Token xsp;
-        while (true) {
-            xsp = jjScanpos;
-            if (jj_3R_31()) {
+        xsp = jjScanpos;
+        if (jj_3R_13()) {
+            jjScanpos = xsp;
+            if (jj_3R_14()) {
                 jjScanpos = xsp;
-                break;
+                if (jj_3R_15()) return true;
             }
         }
         return false;
     }
 
-    private boolean jj_3R_16() {
+    private boolean jj_3R_28() {
+        if (jj_3R_30()) return true;
         Token xsp;
-        xsp = jjScanpos;
-        if (jj_3R_19()) {
-            jjScanpos = xsp;
-            if (jj_3R_20()) {
+        while (true) {
+            xsp = jjScanpos;
+            if (jj_3R_31()) {
                 jjScanpos = xsp;
-                if (jj_3R_21()) {
-                    jjScanpos = xsp;
-                    if (jj_3R_22()) {
-                        jjScanpos = xsp;
-                        if (jj_3R_23()) {
-                            jjScanpos = xsp;
-                            if (jj_3R_24()) return true;
-                        }
-                    }
-                }
+                break;
             }
         }
         return false;
@@ -655,18 +716,19 @@ public class SelectorParser implements 
SelectorParserConstants {
 
     private boolean jj_3_3() {
         if (jj_scan_token(NOT)) return true;
-        if (jj_scan_token(BETWEEN)) return true;
-        if (jj_3R_7()) return true;
-        if (jj_scan_token(AND)) return true;
-        if (jj_3R_7()) return true;
+        if (jj_scan_token(STARTSWITH)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
     private boolean jj_3R_41() {
-        if (jj_scan_token(BETWEEN)) return true;
-        if (jj_3R_7()) return true;
-        if (jj_scan_token(AND)) return true;
-        if (jj_3R_7()) return true;
+        if (jj_scan_token(STARTSWITH)) return true;
+        if (jj_3R_27()) return true;
+        return false;
+    }
+
+    private boolean jj_3R_11() {
+        if (jj_3R_12()) return true;
         return false;
     }
 
@@ -676,6 +738,12 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
+    private boolean jj_3_7() {
+        if (jj_scan_token(34)) return true;
+        if (jj_3R_7()) return true;
+        return false;
+    }
+
     private boolean jj_3_2() {
         if (jj_scan_token(NOT)) return true;
         if (jj_scan_token(CONTAINS)) return true;
@@ -683,26 +751,26 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_15() {
-        if (jj_scan_token(29)) return true;
-        if (jj_3R_18()) return true;
-        if (jj_scan_token(31)) return true;
+    private boolean jj_3R_10() {
+        if (jj_scan_token(NOT)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_14() {
-        if (jj_3R_17()) return true;
+    private boolean jj_3R_40() {
+        if (jj_scan_token(CONTAINS)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
-    private boolean jj_3R_13() {
-        if (jj_3R_16()) return true;
+    private boolean jj_3R_9() {
+        if (jj_scan_token(35)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_40() {
-        if (jj_scan_token(CONTAINS)) return true;
-        if (jj_3R_27()) return true;
+    private boolean jj_3R_27() {
+        if (jj_scan_token(STRING_LITERAL)) return true;
         return false;
     }
 
@@ -719,38 +787,43 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_17() {
-        if (jj_scan_token(ID)) return true;
+    private boolean jj_3R_8() {
+        if (jj_scan_token(34)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_12() {
+    private boolean jj_3R_39() {
+        if (jj_scan_token(30)) return true;
+        if (jj_3R_7()) return true;
+        return false;
+    }
+
+    private boolean jj_3R_7() {
         Token xsp;
         xsp = jjScanpos;
-        if (jj_3R_13()) {
+        if (jj_3R_8()) {
             jjScanpos = xsp;
-            if (jj_3R_14()) {
+            if (jj_3R_9()) {
                 jjScanpos = xsp;
-                if (jj_3R_15()) return true;
+                if (jj_3R_10()) {
+                    jjScanpos = xsp;
+                    if (jj_3R_11()) return true;
+                }
             }
         }
         return false;
     }
 
-    private boolean jj_3R_39() {
-        if (jj_scan_token(28)) return true;
-        if (jj_3R_7()) return true;
-        return false;
-    }
-
     private boolean jj_3R_38() {
-        if (jj_scan_token(27)) return true;
+        if (jj_scan_token(29)) return true;
         if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_11() {
-        if (jj_3R_12()) return true;
+    private boolean jj_3R_46() {
+        if (jj_scan_token(32)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
@@ -761,19 +834,18 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private boolean jj_3R_37() {
-        if (jj_scan_token(26)) return true;
+        if (jj_scan_token(28)) return true;
         if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3_5() {
-        if (jj_scan_token(32)) return true;
-        if (jj_3R_7()) return true;
+    private boolean jj_3R_24() {
+        if (jj_scan_token(NULL)) return true;
         return false;
     }
 
-    private boolean jj_3R_10() {
-        if (jj_scan_token(NOT)) return true;
+    private boolean jj_3R_36() {
+        if (jj_scan_token(27)) return true;
         if (jj_3R_7()) return true;
         return false;
     }
@@ -799,7 +871,19 @@ public class SelectorParser implements 
SelectorParserConstants {
                                         jjScanpos = xsp;
                                         if (jj_3R_42()) {
                                             jjScanpos = xsp;
-                                            if (jj_3_4()) return true;
+                                            if (jj_3_4()) {
+                                                jjScanpos = xsp;
+                                                if (jj_3R_43()) {
+                                                    jjScanpos = xsp;
+                                                    if (jj_3_5()) {
+                                                        jjScanpos = xsp;
+                                                        if (jj_3R_44()) {
+                                                            jjScanpos = xsp;
+                                                            if (jj_3_6()) 
return true;
+                                                        }
+                                                    }
+                                                }
+                                            }
                                         }
                                     }
                                 }
@@ -812,20 +896,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_36() {
-        if (jj_scan_token(25)) return true;
-        if (jj_3R_7()) return true;
-        return false;
-    }
-
-    private boolean jj_3R_9() {
-        if (jj_scan_token(33)) return true;
-        if (jj_3R_7()) return true;
-        return false;
-    }
-
-    private boolean jj_3R_27() {
-        if (jj_scan_token(STRING_LITERAL)) return true;
+    private boolean jj_3R_23() {
+        if (jj_scan_token(FALSE)) return true;
         return false;
     }
 
@@ -842,30 +914,30 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_8() {
-        if (jj_scan_token(32)) return true;
-        if (jj_3R_7()) return true;
+    private boolean jj_3R_22() {
+        if (jj_scan_token(TRUE)) return true;
         return false;
     }
 
-    private boolean jj_3R_7() {
+    private boolean jj_3_6() {
+        if (jj_scan_token(NOT)) return true;
+        if (jj_scan_token(IN)) return true;
+        if (jj_scan_token(31)) return true;
+        if (jj_3R_27()) return true;
         Token xsp;
-        xsp = jjScanpos;
-        if (jj_3R_8()) {
-            jjScanpos = xsp;
-            if (jj_3R_9()) {
+        while (true) {
+            xsp = jjScanpos;
+            if (jj_3R_46()) {
                 jjScanpos = xsp;
-                if (jj_3R_10()) {
-                    jjScanpos = xsp;
-                    if (jj_3R_11()) return true;
-                }
+                break;
             }
         }
+        if (jj_scan_token(33)) return true;
         return false;
     }
 
-    private boolean jj_3R_44() {
-        if (jj_scan_token(30)) return true;
+    private boolean jj_3R_45() {
+        if (jj_scan_token(32)) return true;
         if (jj_3R_27()) return true;
         return false;
     }
@@ -883,13 +955,13 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_24() {
-        if (jj_scan_token(NULL)) return true;
+    private boolean jj_3R_21() {
+        if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
         return false;
     }
 
-    private boolean jj_3R_23() {
-        if (jj_scan_token(FALSE)) return true;
+    private boolean jj_3R_20() {
+        if (jj_scan_token(DECIMAL_LITERAL)) return true;
         return false;
     }
 
@@ -900,25 +972,24 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_22() {
-        if (jj_scan_token(TRUE)) return true;
-        return false;
-    }
-
-    private boolean jj_3_4() {
-        if (jj_scan_token(NOT)) return true;
+    private boolean jj_3R_44() {
         if (jj_scan_token(IN)) return true;
-        if (jj_scan_token(29)) return true;
+        if (jj_scan_token(31)) return true;
         if (jj_3R_27()) return true;
         Token xsp;
         while (true) {
             xsp = jjScanpos;
-            if (jj_3R_44()) {
+            if (jj_3R_45()) {
                 jjScanpos = xsp;
                 break;
             }
         }
-        if (jj_scan_token(31)) return true;
+        if (jj_scan_token(33)) return true;
+        return false;
+    }
+
+    private boolean jj_3R_19() {
+        if (jj_3R_27()) return true;
         return false;
     }
 
@@ -928,9 +999,34 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_43() {
-        if (jj_scan_token(30)) return true;
-        if (jj_3R_27()) return true;
+    private boolean jj_3R_16() {
+        Token xsp;
+        xsp = jjScanpos;
+        if (jj_3R_19()) {
+            jjScanpos = xsp;
+            if (jj_3R_20()) {
+                jjScanpos = xsp;
+                if (jj_3R_21()) {
+                    jjScanpos = xsp;
+                    if (jj_3R_22()) {
+                        jjScanpos = xsp;
+                        if (jj_3R_23()) {
+                            jjScanpos = xsp;
+                            if (jj_3R_24()) return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3_5() {
+        if (jj_scan_token(NOT)) return true;
+        if (jj_scan_token(BETWEEN)) return true;
+        if (jj_3R_7()) return true;
+        if (jj_scan_token(AND)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
@@ -951,7 +1047,7 @@ public class SelectorParser implements 
SelectorParserConstants {
     private Token jjScanpos, jjLastpos;
     private int jjLa;
     private int jjGen;
-    final private int[] jjLa1 = new int[14];
+    final private int[] jjLa1 = new int[16];
     static private int[] jjLa10;
     static private int[] jjLa11;
 
@@ -961,14 +1057,14 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private static void jj_la1_init_0() {
-        jjLa10 = new int[]{0x400, 0x200, 0x1810000, 0x1800000, 0x10000, 
0x1e021900, 0x40000000, 0x40000000, 0x1e020000, 0x800, 0x1000, 0x206ce100, 
0x206ce000, 0x2ce000,};
+        jjLa10 = new int[]{0x400, 0x200, 0x6010000, 0x6000000, 0x10000, 
0x780e1900, 0x0, 0x0, 0x78020000, 0x40000, 0x80000, 0x800, 0x1000, 0x81b0e100, 
0x81b0e000, 0xb0e000,};
     }
 
     private static void jj_la1_init_1() {
-        jjLa11 = new int[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x2, 0x0, 0x0,};
+        jjLa11 = new int[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x8, 0x0, 0x0,};
     }
 
-    final private JJCalls[] jj2Rtns = new JJCalls[5];
+    final private JJCalls[] jj2Rtns = new JJCalls[7];
     private boolean jjRescan = false;
     private int jjGc = 0;
 
@@ -992,7 +1088,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1016,7 +1112,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1029,7 +1125,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1042,7 +1138,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1054,7 +1150,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1066,7 +1162,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < 16; i++) jjLa1[i] = -1;
         for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
@@ -1170,19 +1266,21 @@ public class SelectorParser implements 
SelectorParserConstants {
             for (int i = 0; i < jjEndpos; i++) {
                 jjExpentry[i] = jjLasttokens[i];
             }
-            jj_entries_loop:
+            boolean exists = false;
             for (java.util.Iterator<?> it = jjExpentries.iterator(); 
it.hasNext(); ) {
+                exists = true;
                 int[] oldentry = (int[]) (it.next());
                 if (oldentry.length == jjExpentry.length) {
                     for (int i = 0; i < jjExpentry.length; i++) {
                         if (oldentry[i] != jjExpentry[i]) {
-                            continue jj_entries_loop;
+                            exists = false;
+                            break;
                         }
                     }
-                    jjExpentries.add(jjExpentry);
-                    break jj_entries_loop;
+                    if (exists) break;
                 }
             }
+            if (!exists) jjExpentries.add(jjExpentry);
             if (pos != 0) jjLasttokens[(jjEndpos = pos) - 1] = kind;
         }
     }
@@ -1192,12 +1290,12 @@ public class SelectorParser implements 
SelectorParserConstants {
      */
     public ParseException generateParseException() {
         jjExpentries.clear();
-        boolean[] la1tokens = new boolean[34];
+        boolean[] la1tokens = new boolean[36];
         if (jjKind >= 0) {
             la1tokens[jjKind] = true;
             jjKind = -1;
         }
-        for (int i = 0; i < 14; i++) {
+        for (int i = 0; i < 16; i++) {
             if (jjLa1[i] == jjGen) {
                 for (int j = 0; j < 32; j++) {
                     if ((jjLa10[i] & (1 << j)) != 0) {
@@ -1209,7 +1307,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                 }
             }
         }
-        for (int i = 0; i < 34; i++) {
+        for (int i = 0; i < 36; i++) {
             if (la1tokens[i]) {
                 jjExpentry = new int[1];
                 jjExpentry[0] = i;
@@ -1240,7 +1338,7 @@ public class SelectorParser implements 
SelectorParserConstants {
 
     private void jj_rescan_token() {
         jjRescan = true;
-        for (int i = 0; i < 5; i++) {
+        for (int i = 0; i < 7; i++) {
             try {
                 JJCalls p = jj2Rtns[i];
                 do {
@@ -1263,6 +1361,12 @@ public class SelectorParser implements 
SelectorParserConstants {
                             case 4:
                                 jj_3_5();
                                 break;
+                            case 5:
+                                jj_3_6();
+                                break;
+                            case 6:
+                                jj_3_7();
+                                break;
                         }
                     }
                     p = p.next;
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj
index 09e03d9bb..f26369692 100644
--- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj
+++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.jj
@@ -170,6 +170,8 @@ TOKEN [IGNORE_CASE] :
   | <  NULL    : "NULL" >
   | <  IS      : "IS" >
   | <  CONTAINS    : "CONTAINS">
+  | <  STARTSWITH  : "STARTSWITH">
+  | <  ENDSWITH    : "ENDSWITH">
 }
 
 /* Literals */
@@ -333,6 +335,28 @@ Expression comparisonExpression() :
                 {
                     left = ComparisonExpression.createNotContains(left, t);
                 }
+            |
+                <STARTSWITH> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createStartsWith(left, t);
+                }
+            |
+                LOOKAHEAD(2)
+                <NOT> <STARTSWITH> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createNotStartsWith(left, t);
+                }
+            |
+                <ENDSWITH> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createEndsWith(left, t);
+                }
+            |
+                LOOKAHEAD(2)
+                <NOT> <ENDSWITH> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createNotEndsWith(left, t);
+                }
             |
                 <BETWEEN> low = unaryExpr() <AND> high = unaryExpr()
                 {
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java
 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java
index 8f849cb51..8f228be8b 100644
--- 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java
+++ 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserConstants.java
@@ -79,23 +79,31 @@ public interface SelectorParserConstants {
     /**
      * RegularExpression Id.
      */
-    int DECIMAL_LITERAL = 18;
+    int STARTSWITH = 18;
     /**
      * RegularExpression Id.
      */
-    int FLOATING_POINT_LITERAL = 19;
+    int ENDSWITH = 19;
     /**
      * RegularExpression Id.
      */
-    int EXPONENT = 20;
+    int DECIMAL_LITERAL = 20;
     /**
      * RegularExpression Id.
      */
-    int STRING_LITERAL = 21;
+    int FLOATING_POINT_LITERAL = 21;
     /**
      * RegularExpression Id.
      */
-    int ID = 22;
+    int EXPONENT = 22;
+    /**
+     * RegularExpression Id.
+     */
+    int STRING_LITERAL = 23;
+    /**
+     * RegularExpression Id.
+     */
+    int ID = 24;
 
     /**
      * Lexical state.
@@ -124,6 +132,8 @@ public interface SelectorParserConstants {
         "\"NULL\"",
         "\"IS\"",
         "\"CONTAINS\"",
+        "\"STARTSWITH\"",
+        "\"ENDSWITH\"",
         "<DECIMAL_LITERAL>",
         "<FLOATING_POINT_LITERAL>",
         "<EXPONENT>",
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java
 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java
index 9d42eea71..6d9b85517 100644
--- 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java
+++ 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParserTokenManager.java
@@ -59,35 +59,37 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 jjmatchedKind = 1;
                 return jjMoveNfa_0(5, 0);
             case 40:
-                jjmatchedKind = 29;
+                jjmatchedKind = 31;
                 return jjMoveNfa_0(5, 0);
             case 41:
-                jjmatchedKind = 31;
+                jjmatchedKind = 33;
                 return jjMoveNfa_0(5, 0);
             case 43:
-                jjmatchedKind = 32;
+                jjmatchedKind = 34;
                 return jjMoveNfa_0(5, 0);
             case 44:
-                jjmatchedKind = 30;
+                jjmatchedKind = 32;
                 return jjMoveNfa_0(5, 0);
             case 45:
-                jjmatchedKind = 33;
+                jjmatchedKind = 35;
                 return jjMoveNfa_0(5, 0);
             case 60:
-                jjmatchedKind = 27;
-                return jjMoveStringLiteralDfa1_0(0x11000000L);
+                jjmatchedKind = 29;
+                return jjMoveStringLiteralDfa1_0(0x44000000L);
             case 61:
-                jjmatchedKind = 23;
+                jjmatchedKind = 25;
                 return jjMoveNfa_0(5, 0);
             case 62:
-                jjmatchedKind = 25;
-                return jjMoveStringLiteralDfa1_0(0x4000000L);
+                jjmatchedKind = 27;
+                return jjMoveStringLiteralDfa1_0(0x10000000L);
             case 65:
                 return jjMoveStringLiteralDfa1_0(0x200L);
             case 66:
                 return jjMoveStringLiteralDfa1_0(0x800L);
             case 67:
                 return jjMoveStringLiteralDfa1_0(0x20000L);
+            case 69:
+                return jjMoveStringLiteralDfa1_0(0x80000L);
             case 70:
                 return jjMoveStringLiteralDfa1_0(0x4000L);
             case 73:
@@ -96,6 +98,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 return jjMoveStringLiteralDfa1_0(0x8100L);
             case 79:
                 return jjMoveStringLiteralDfa1_0(0x400L);
+            case 83:
+                return jjMoveStringLiteralDfa1_0(0x40000L);
             case 84:
                 return jjMoveStringLiteralDfa1_0(0x2000L);
             case 97:
@@ -104,6 +108,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 return jjMoveStringLiteralDfa1_0(0x800L);
             case 99:
                 return jjMoveStringLiteralDfa1_0(0x20000L);
+            case 101:
+                return jjMoveStringLiteralDfa1_0(0x80000L);
             case 102:
                 return jjMoveStringLiteralDfa1_0(0x4000L);
             case 105:
@@ -112,6 +118,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 return jjMoveStringLiteralDfa1_0(0x8100L);
             case 111:
                 return jjMoveStringLiteralDfa1_0(0x400L);
+            case 115:
+                return jjMoveStringLiteralDfa1_0(0x40000L);
             case 116:
                 return jjMoveStringLiteralDfa1_0(0x2000L);
             default:
@@ -127,17 +135,17 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         }
         switch (curChar) {
             case 61:
-                if ((active0 & 0x4000000L) != 0L) {
-                    jjmatchedKind = 26;
-                    jjmatchedPos = 1;
-                } else if ((active0 & 0x10000000L) != 0L) {
+                if ((active0 & 0x10000000L) != 0L) {
                     jjmatchedKind = 28;
                     jjmatchedPos = 1;
+                } else if ((active0 & 0x40000000L) != 0L) {
+                    jjmatchedKind = 30;
+                    jjmatchedPos = 1;
                 }
                 break;
             case 62:
-                if ((active0 & 0x1000000L) != 0L) {
-                    jjmatchedKind = 24;
+                if ((active0 & 0x4000000L) != 0L) {
+                    jjmatchedKind = 26;
                     jjmatchedPos = 1;
                 }
                 break;
@@ -150,7 +158,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedKind = 12;
                     jjmatchedPos = 1;
                 }
-                return jjMoveStringLiteralDfa2_0(active0, 0x200L);
+                return jjMoveStringLiteralDfa2_0(active0, 0x80200L);
             case 79:
                 return jjMoveStringLiteralDfa2_0(active0, 0x20100L);
             case 82:
@@ -165,6 +173,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 1;
                 }
                 break;
+            case 84:
+                return jjMoveStringLiteralDfa2_0(active0, 0x40000L);
             case 85:
                 return jjMoveStringLiteralDfa2_0(active0, 0x8000L);
             case 97:
@@ -176,7 +186,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedKind = 12;
                     jjmatchedPos = 1;
                 }
-                return jjMoveStringLiteralDfa2_0(active0, 0x200L);
+                return jjMoveStringLiteralDfa2_0(active0, 0x80200L);
             case 111:
                 return jjMoveStringLiteralDfa2_0(active0, 0x20100L);
             case 114:
@@ -191,6 +201,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 1;
                 }
                 break;
+            case 116:
+                return jjMoveStringLiteralDfa2_0(active0, 0x40000L);
             case 117:
                 return jjMoveStringLiteralDfa2_0(active0, 0x8000L);
             default:
@@ -208,12 +220,14 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
             return jjMoveNfa_0(5, 1);
         }
         switch (curChar) {
+            case 65:
+                return jjMoveStringLiteralDfa3_0(active0, 0x40000L);
             case 68:
                 if ((active0 & 0x200L) != 0L) {
                     jjmatchedKind = 9;
                     jjmatchedPos = 2;
                 }
-                break;
+                return jjMoveStringLiteralDfa3_0(active0, 0x80000L);
             case 76:
                 return jjMoveStringLiteralDfa3_0(active0, 0xc000L);
             case 78:
@@ -226,12 +240,14 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 return jjMoveStringLiteralDfa3_0(active0, 0x800L);
             case 85:
                 return jjMoveStringLiteralDfa3_0(active0, 0x2000L);
+            case 97:
+                return jjMoveStringLiteralDfa3_0(active0, 0x40000L);
             case 100:
                 if ((active0 & 0x200L) != 0L) {
                     jjmatchedKind = 9;
                     jjmatchedPos = 2;
                 }
-                break;
+                return jjMoveStringLiteralDfa3_0(active0, 0x80000L);
             case 108:
                 return jjMoveStringLiteralDfa3_0(active0, 0xc000L);
             case 110:
@@ -271,8 +287,10 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 3;
                 }
                 break;
+            case 82:
+                return jjMoveStringLiteralDfa4_0(active0, 0x40000L);
             case 83:
-                return jjMoveStringLiteralDfa4_0(active0, 0x4000L);
+                return jjMoveStringLiteralDfa4_0(active0, 0x84000L);
             case 84:
                 return jjMoveStringLiteralDfa4_0(active0, 0x20000L);
             case 87:
@@ -289,8 +307,10 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 3;
                 }
                 break;
+            case 114:
+                return jjMoveStringLiteralDfa4_0(active0, 0x40000L);
             case 115:
-                return jjMoveStringLiteralDfa4_0(active0, 0x4000L);
+                return jjMoveStringLiteralDfa4_0(active0, 0x84000L);
             case 116:
                 return jjMoveStringLiteralDfa4_0(active0, 0x20000L);
             case 119:
@@ -318,6 +338,10 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 4;
                 }
                 return jjMoveStringLiteralDfa5_0(active0, 0x800L);
+            case 84:
+                return jjMoveStringLiteralDfa5_0(active0, 0x40000L);
+            case 87:
+                return jjMoveStringLiteralDfa5_0(active0, 0x80000L);
             case 97:
                 return jjMoveStringLiteralDfa5_0(active0, 0x20000L);
             case 101:
@@ -326,6 +350,10 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 4;
                 }
                 return jjMoveStringLiteralDfa5_0(active0, 0x800L);
+            case 116:
+                return jjMoveStringLiteralDfa5_0(active0, 0x40000L);
+            case 119:
+                return jjMoveStringLiteralDfa5_0(active0, 0x80000L);
             default:
                 break;
         }
@@ -344,11 +372,15 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
             case 69:
                 return jjMoveStringLiteralDfa6_0(active0, 0x800L);
             case 73:
-                return jjMoveStringLiteralDfa6_0(active0, 0x20000L);
+                return jjMoveStringLiteralDfa6_0(active0, 0xa0000L);
+            case 83:
+                return jjMoveStringLiteralDfa6_0(active0, 0x40000L);
             case 101:
                 return jjMoveStringLiteralDfa6_0(active0, 0x800L);
             case 105:
-                return jjMoveStringLiteralDfa6_0(active0, 0x20000L);
+                return jjMoveStringLiteralDfa6_0(active0, 0xa0000L);
+            case 115:
+                return jjMoveStringLiteralDfa6_0(active0, 0x40000L);
             default:
                 break;
         }
@@ -370,12 +402,20 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedPos = 6;
                 }
                 return jjMoveStringLiteralDfa7_0(active0, 0x20000L);
+            case 84:
+                return jjMoveStringLiteralDfa7_0(active0, 0x80000L);
+            case 87:
+                return jjMoveStringLiteralDfa7_0(active0, 0x40000L);
             case 110:
                 if ((active0 & 0x800L) != 0L) {
                     jjmatchedKind = 11;
                     jjmatchedPos = 6;
                 }
                 return jjMoveStringLiteralDfa7_0(active0, 0x20000L);
+            case 116:
+                return jjMoveStringLiteralDfa7_0(active0, 0x80000L);
+            case 119:
+                return jjMoveStringLiteralDfa7_0(active0, 0x40000L);
             default:
                 break;
         }
@@ -391,12 +431,28 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
             return jjMoveNfa_0(5, 6);
         }
         switch (curChar) {
+            case 72:
+                if ((active0 & 0x80000L) != 0L) {
+                    jjmatchedKind = 19;
+                    jjmatchedPos = 7;
+                }
+                break;
+            case 73:
+                return jjMoveStringLiteralDfa8_0(active0, 0x40000L);
             case 83:
                 if ((active0 & 0x20000L) != 0L) {
                     jjmatchedKind = 17;
                     jjmatchedPos = 7;
                 }
                 break;
+            case 104:
+                if ((active0 & 0x80000L) != 0L) {
+                    jjmatchedKind = 19;
+                    jjmatchedPos = 7;
+                }
+                break;
+            case 105:
+                return jjMoveStringLiteralDfa8_0(active0, 0x40000L);
             case 115:
                 if ((active0 & 0x20000L) != 0L) {
                     jjmatchedKind = 17;
@@ -409,6 +465,52 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         return jjMoveNfa_0(5, 7);
     }
 
+    private int jjMoveStringLiteralDfa8_0(long old0, long active0) {
+        if (((active0 &= old0)) == 0L)
+            return jjMoveNfa_0(5, 7);
+        try {
+            curChar = inputStream.readChar();
+        } catch (java.io.IOException e) {
+            return jjMoveNfa_0(5, 7);
+        }
+        switch (curChar) {
+            case 84:
+                return jjMoveStringLiteralDfa9_0(active0, 0x40000L);
+            case 116:
+                return jjMoveStringLiteralDfa9_0(active0, 0x40000L);
+            default:
+                break;
+        }
+        return jjMoveNfa_0(5, 8);
+    }
+
+    private int jjMoveStringLiteralDfa9_0(long old0, long active0) {
+        if (((active0 &= old0)) == 0L)
+            return jjMoveNfa_0(5, 8);
+        try {
+            curChar = inputStream.readChar();
+        } catch (java.io.IOException e) {
+            return jjMoveNfa_0(5, 8);
+        }
+        switch (curChar) {
+            case 72:
+                if ((active0 & 0x40000L) != 0L) {
+                    jjmatchedKind = 18;
+                    jjmatchedPos = 9;
+                }
+                break;
+            case 104:
+                if ((active0 & 0x40000L) != 0L) {
+                    jjmatchedKind = 18;
+                    jjmatchedPos = 9;
+                }
+                break;
+            default:
+                break;
+        }
+        return jjMoveNfa_0(5, 9);
+    }
+
     static final long[] JJ_BIT_VEC_0 = {
         0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 
0xffffffffffffffffL
     };
@@ -443,8 +545,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             if ((0x3ff000000000000L & l) != 0L)
                                 jjCheckNAddStates(0, 3);
                             else if (curChar == 36) {
-                                if (kind > 22)
-                                    kind = 22;
+                                if (kind > 24)
+                                    kind = 24;
                                 jjCheckNAdd(28);
                             } else if (curChar == 39)
                                 jjCheckNAddStates(4, 6);
@@ -455,12 +557,12 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             else if (curChar == 45)
                                 jjstateSet[jjnewStateCnt++] = 0;
                             if ((0x3fe000000000000L & l) != 0L) {
-                                if (kind > 18)
-                                    kind = 18;
+                                if (kind > 20)
+                                    kind = 20;
                                 jjCheckNAddTwoStates(15, 16);
                             } else if (curChar == 48) {
-                                if (kind > 18)
-                                    kind = 18;
+                                if (kind > 20)
+                                    kind = 20;
                             }
                             break;
                         case 0:
@@ -512,21 +614,21 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                                 jjstateSet[jjnewStateCnt++] = 6;
                             break;
                         case 13:
-                            if (curChar == 48 && kind > 18)
-                                kind = 18;
+                            if (curChar == 48 && kind > 20)
+                                kind = 20;
                             break;
                         case 14:
                             if ((0x3fe000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 20)
+                                kind = 20;
                             jjCheckNAddTwoStates(15, 16);
                             break;
                         case 15:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 20)
+                                kind = 20;
                             jjCheckNAddTwoStates(15, 16);
                             break;
                         case 17:
@@ -536,8 +638,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 18:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAddTwoStates(18, 19);
                             break;
                         case 20:
@@ -547,8 +649,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 21:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAdd(21);
                             break;
                         case 22:
@@ -565,21 +667,21 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                                 jjCheckNAddStates(4, 6);
                             break;
                         case 26:
-                            if (curChar == 39 && kind > 21)
-                                kind = 21;
+                            if (curChar == 39 && kind > 23)
+                                kind = 23;
                             break;
                         case 27:
                             if (curChar != 36)
                                 break;
-                            if (kind > 22)
-                                kind = 22;
+                            if (kind > 24)
+                                kind = 24;
                             jjCheckNAdd(28);
                             break;
                         case 28:
                             if ((0x3ff001000000000L & l) == 0L)
                                 break;
-                            if (kind > 22)
-                                kind = 22;
+                            if (kind > 24)
+                                kind = 24;
                             jjCheckNAdd(28);
                             break;
                         case 29:
@@ -593,15 +695,15 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 31:
                             if (curChar != 46)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAddTwoStates(32, 33);
                             break;
                         case 32:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAddTwoStates(32, 33);
                             break;
                         case 34:
@@ -611,8 +713,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 35:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAdd(35);
                             break;
                         case 36:
@@ -626,8 +728,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 39:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 19)
-                                kind = 19;
+                            if (kind > 21)
+                                kind = 21;
                             jjCheckNAdd(39);
                             break;
                         default:
@@ -642,8 +744,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 28:
                             if ((0x7fffffe87fffffeL & l) == 0L)
                                 break;
-                            if (kind > 22)
-                                kind = 22;
+                            if (kind > 24)
+                                kind = 24;
                             jjCheckNAdd(28);
                             break;
                         case 1:
@@ -657,8 +759,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             jjCheckNAddTwoStates(10, 8);
                             break;
                         case 16:
-                            if ((0x100000001000L & l) != 0L && kind > 18)
-                                kind = 18;
+                            if ((0x100000001000L & l) != 0L && kind > 20)
+                                kind = 20;
                             break;
                         case 19:
                             if ((0x2000000020L & l) != 0L)
@@ -766,8 +868,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
      */
     public static final String[] JJ_STR_LITERAL_IMAGES = {
         "", null, null, null, null, null, null, null, null, null, null, null, 
null,
-        null, null, null, null, null, null, null, null, null, null, "\75", 
"\74\76", "\76",
-        "\76\75", "\74", "\74\75", "\50", "\54", "\51", "\53", "\55",};
+        null, null, null, null, null, null, null, null, null, null, null, 
null, "\75",
+        "\74\76", "\76", "\76\75", "\74", "\74\75", "\50", "\54", "\51", 
"\53", "\55",};
 
     /**
      * Lexer state names.
@@ -776,7 +878,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         "DEFAULT",
     };
     static final long[] JJ_TO_TOKEN = {
-        0x3ffefff01L,
+        0xfffbfff01L,
     };
     static final long[] JJ_TO_SKIP = {
         0xfeL,
@@ -836,8 +938,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
      */
     public void SwitchTo(int lexState) {
         if (lexState >= 1 || lexState < 0)
-            throw new TokenMgrError("Error: Ignoring invalid lexical state : " 
+ lexState + ". State unchanged.",
-                TokenMgrError.INVALID_LEXICAL_STATE);
+            throw new TokenMgrError("Error: Ignoring invalid lexical state : " 
+ lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
         else
             curLexState = lexState;
     }
@@ -934,8 +1035,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 inputStream.backup(1);
                 errorAfter = curPos <= 1 ? "" : inputStream.GetImage();
             }
-            throw new TokenMgrError(eofSeen, curLexState, errorLine, 
errorColumn, errorAfter, curChar,
-                TokenMgrError.LEXICAL_ERROR);
+            throw new TokenMgrError(eofSeen, curLexState, errorLine, 
errorColumn, errorAfter, curChar, TokenMgrError.LEXICAL_ERROR);
         }
     }
 
diff --git 
a/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java 
b/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java
index fa6b04af4..df883458e 100644
--- a/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java
+++ b/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java
@@ -48,142 +48,157 @@ public class ExpressionTest {
 
 
     @Test
-    public void testConstains_has() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_has() throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "axb")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("value contains 'x'"), context, Boolean.TRUE);
+        eval(genExp("value startswith 'ax'"), context, Boolean.TRUE);
+        eval(genExp("value endswith 'xb'"), context, Boolean.TRUE);
     }
 
     @Test
-    public void test_notConstains_has() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_has() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "axb")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not startswith 'ax'"), context, Boolean.FALSE);
+        eval(genExp("value not endswith 'xb'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_has_not() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_has_not() throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "abb")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_has_not() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_has_not() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "abb")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("value not contains 'x'"), context, Boolean.TRUE);
+        eval(genExp("value not startswith 'x'"), context, Boolean.TRUE);
+        eval(genExp("value not endswith 'x'"), context, Boolean.TRUE);
     }
 
     @Test
-    public void testConstains_hasEmpty() throws Exception {
-        Expression expr = genExp("value contains ''");
+    public void testContains_StartsWith_EndsWith_hasEmpty() throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "axb")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains ''"), context, Boolean.FALSE);
+        eval(genExp("value startswith ''"), context, Boolean.FALSE);
+        eval(genExp("value endswith ''"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_hasEmpty() throws Exception {
-        Expression expr = genExp("value not contains ''");
+    public void test_notContains_notStartsWith_notEndsWith_hasEmpty() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", "axb")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains ''"), context, Boolean.FALSE);
+        eval(genExp("value not startswith ''"), context, Boolean.FALSE);
+        eval(genExp("value not endswith ''"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_null_has_1() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_null_has_1() throws Exception 
{
         EvaluationContext context = genContext(
                 KeyValue.c("value", null)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_null_has_1() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_null_has_1() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", null)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_null_has_2() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_null_has_2() throws Exception 
{
         EvaluationContext context = genContext(
 //                KeyValue.c("value", null)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_null_has_2() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_null_has_2() throws 
Exception {
         EvaluationContext context = genContext(
 //                KeyValue.c("value", null)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_number_has() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_number_has() throws Exception 
{
         EvaluationContext context = genContext(
                 KeyValue.c("value", 1.23)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_number_has() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_number_has() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", 1.23)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_boolean_has() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_boolean_has() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", Boolean.TRUE)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_boolean_has() throws Exception {
-        Expression expr = genExp("value not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_boolean_has() 
throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("value", Boolean.TRUE)
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value not endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_object_has() throws Exception {
-        Expression expr = genExp("value contains 'x'");
+    public void testContains_StartsWith_EndsWith_object_has() throws Exception 
{
         EvaluationContext context = genContext(
                 KeyValue.c("value", new Object())
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("value contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("value startswith 'x'"), context, Boolean.FALSE);
+        eval(genExp("value endswith 'x'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_has_not_string_1() throws Exception {
+    public void testContains_has_not_string_1() throws Exception {
         try {
             Expression expr = genExp("value contains x");  // will throw parse 
exception.
             EvaluationContext context = genContext(
@@ -195,7 +210,7 @@ public class ExpressionTest {
     }
 
     @Test
-    public void test_notConstains_has_not_string_1() throws Exception {
+    public void test_notContains_has_not_string_1() throws Exception {
         try {
             Expression expr = genExp("value not contains x");  // will throw 
parse exception.
             EvaluationContext context = genContext(
@@ -207,7 +222,7 @@ public class ExpressionTest {
     }
 
     @Test
-    public void testConstains_has_not_string_2() throws Exception {
+    public void testContains_has_not_string_2() throws Exception {
         try {
             Expression expr = genExp("value contains 123");  // will throw 
parse exception.
             EvaluationContext context = genContext(
@@ -219,7 +234,7 @@ public class ExpressionTest {
     }
 
     @Test
-    public void test_notConstains_has_not_string_2() throws Exception {
+    public void test_notContains_has_not_string_2() throws Exception {
         try {
             Expression expr = genExp("value not contains 123");  // will throw 
parse exception.
             EvaluationContext context = genContext(
@@ -231,79 +246,87 @@ public class ExpressionTest {
     }
 
     @Test
-    public void testConstains_string_has_string() throws Exception {
-        Expression expr = genExp("'axb' contains 'x'");
+    public void testContains_StartsWith_EndsWith_string_has_string() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("'axb' contains 'x'"), context, Boolean.TRUE);
+        eval(genExp("'axb' startswith 'ax'"), context, Boolean.TRUE);
+        eval(genExp("'axb' endswith 'xb'"), context, Boolean.TRUE);
     }
 
     @Test
-    public void test_notConstains_string_has_string() throws Exception {
-        Expression expr = genExp("'axb' not contains 'x'");
+    public void test_notContains_notStartsWith_notEndsWith_string_has_string() 
throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'axb' not contains 'x'"), context, Boolean.FALSE);
+        eval(genExp("'axb' not startswith 'ax'"), context, Boolean.FALSE);
+        eval(genExp("'axb' not endswith 'xb'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_string_has_not_string() throws Exception {
-        Expression expr = genExp("'axb' contains 'u'");
+    public void testContains_startsWith_endsWith_string_has_not_string() 
throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'axb' contains 'u'"), context, Boolean.FALSE);
+        eval(genExp("'axb' startswith 'u'"), context, Boolean.FALSE);
+        eval(genExp("'axb' endswith 'u'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_string_has_not_string() throws Exception {
-        Expression expr = genExp("'axb' not contains 'u'");
+    public void 
test_notContains_notStartsWith_notEndsWith_string_has_not_string() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("'axb' not contains 'u'"), context, Boolean.TRUE);
+        eval(genExp("'axb' not startswith 'u'"), context, Boolean.TRUE);
+        eval(genExp("'axb' not endswith 'u'"), context, Boolean.TRUE);
     }
 
     @Test
-    public void testConstains_string_has_empty() throws Exception {
-        Expression expr = genExp("'axb' contains ''");
+    public void testContains_StartsWith_EndsWith_string_has_empty() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'axb' contains ''"), context, Boolean.FALSE);
+        eval(genExp("'axb' startswith ''"), context, Boolean.FALSE);
+        eval(genExp("'axb' endswith ''"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_string_has_empty() throws Exception {
-        Expression expr = genExp("'axb' not contains ''");
+    public void test_notContains_notStartsWith_notEndsWith_string_has_empty() 
throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'axb' not contains ''"), context, Boolean.FALSE);
+        eval(genExp("'axb' not startswith ''"), context, Boolean.FALSE);
+        eval(genExp("'axb' not endswith ''"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_string_has_space() throws Exception {
-        Expression expr = genExp("' ' contains ' '");
+    public void testContains_StartsWith_EndsWith_string_has_space() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("' ' contains ' '"), context, Boolean.TRUE);
+        eval(genExp("' ' startswith ' '"), context, Boolean.TRUE);
+        eval(genExp("' ' endswith ' '"), context, Boolean.TRUE);
     }
 
     @Test
-    public void test_notConstains_string_has_space() throws Exception {
-        Expression expr = genExp("' ' not contains ' '");
+    public void test_notContains_notStartsWith_notEndsWith_string_has_space() 
throws Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("' ' not contains ' '"), context, Boolean.FALSE);
+        eval(genExp("' ' not startswith ' '"), context, Boolean.FALSE);
+        eval(genExp("' ' not endswith ' '"), context, Boolean.FALSE);
     }
 
     @Test
-    public void testConstains_string_has_nothing() throws Exception {
+    public void testContains_string_has_nothing() throws Exception {
         try {
             Expression expr = genExp("'axb' contains ");  // will throw parse 
exception.
             EvaluationContext context = genContext(
@@ -315,7 +338,7 @@ public class ExpressionTest {
     }
 
     @Test
-    public void test_notConstains_string_has_nothing() throws Exception {
+    public void test_notContains_string_has_nothing() throws Exception {
         try {
             Expression expr = genExp("'axb' not contains ");  // will throw 
parse exception.
             EvaluationContext context = genContext(
@@ -327,30 +350,33 @@ public class ExpressionTest {
     }
 
     @Test
-    public void testConstains_string_has_special_1() throws Exception {
-        Expression expr = genExp("'axb' contains '.'");
+    public void testContains_StartsWith_EndsWith_string_has_special_1() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'axb' contains '.'"), context, Boolean.FALSE);
+        eval(genExp("'axb' startswith '.'"), context, Boolean.FALSE);
+        eval(genExp("'axb' endswith '.'"), context, Boolean.FALSE);
     }
 
     @Test
-    public void test_notConstains_string_has_special_1() throws Exception {
-        Expression expr = genExp("'axb' not contains '.'");
+    public void 
test_notContains_notStartsWith_notEndsWith_string_has_special_1() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.TRUE);
+        eval(genExp("'axb' not contains '.'"), context, Boolean.TRUE);
+        eval(genExp("'axb' not startswith '.'"), context, Boolean.TRUE);
+        eval(genExp("'axb' not endswith '.'"), context, Boolean.TRUE);
     }
 
     @Test
-    public void testConstains_string_has_special_2() throws Exception {
-        Expression expr = genExp("'s' contains '\\'");
+    public void testContains_StartsWith_EndsWith_string_has_special_2() throws 
Exception {
         EvaluationContext context = genContext(
                 KeyValue.c("whatever", "whatever")
         );
-        eval(expr, context, Boolean.FALSE);
+        eval(genExp("'s' contains '\\'"), context, Boolean.FALSE);
+        eval(genExp("'s' startswith '\\'"), context, Boolean.FALSE);
+        eval(genExp("'s' endswith '\\'"), context, Boolean.FALSE);
     }
 
     @Test
@@ -364,6 +390,28 @@ public class ExpressionTest {
         eval(expr, context, Boolean.TRUE);
     }
 
+    @Test
+    public void testStartsWithAllInOne() throws Exception {
+        Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 
10 and c not startswith 'axbc'");
+        EvaluationContext context = genContext(
+                KeyValue.c("a", "3"),
+                KeyValue.c("b", 3),
+                KeyValue.c("c", "axbdc")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void testEndsWithAllInOne() throws Exception {
+        Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 
10 and c not endswith 'axbc'");
+        EvaluationContext context = genContext(
+                KeyValue.c("a", "3"),
+                KeyValue.c("b", 3),
+                KeyValue.c("c", "axbdc")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
     @Test
     public void testEvaluate_stringHasString() throws Exception {
         Expression expr = genExp(stringHasString);


Reply via email to