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

fuyou 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 3ac885705 new feature: sql expression CONTAINS support (#6864)
3ac885705 is described below

commit 3ac885705d9fa36870c1b059e1089c158fa66285
Author: Quan <zsjper...@foxmail.com>
AuthorDate: Fri Jun 16 16:22:46 2023 +0800

    new feature: sql expression CONTAINS support (#6864)
---
 .../filter/expression/ComparisonExpression.java    |  84 +++
 .../rocketmq/filter/parser/ParseException.java     |   2 +-
 .../rocketmq/filter/parser/SelectorParser.java     | 755 ++++++++++-----------
 .../rocketmq/filter/parser/SelectorParser.jj       |  13 +-
 .../filter/parser/SelectorParserConstants.java     |  15 +-
 .../filter/parser/SelectorParserTokenManager.java  | 176 +++--
 .../rocketmq/filter/parser/SimpleCharStream.java   |   2 +-
 .../org/apache/rocketmq/filter/parser/Token.java   |   2 +-
 .../rocketmq/filter/parser/TokenMgrError.java      |   2 +-
 .../org/apache/rocketmq/filter/ExpressionTest.java | 318 +++++++++
 .../org/apache/rocketmq/filter/ParserTest.java     |   4 +-
 11 files changed, 887 insertions(+), 486 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 b793cdf97..ff9d84af0 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
@@ -69,6 +69,90 @@ public abstract class ComparisonExpression extends 
BinaryExpression implements B
         return LogicExpression.createOR(createLessThan(value, left), 
createGreaterThan(value, right));
     }
 
+    static class ContainsExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public ContainsExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "CONTAINS";
+        }
+
+        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).contains(search) ? Boolean.TRUE : 
Boolean.FALSE;
+        }
+
+        public boolean matches(EvaluationContext message) throws Exception {
+            Object object = evaluate(message);
+            return object != null && object == Boolean.TRUE;
+        }
+    }
+
+    static class NotContainsExpression extends UnaryExpression implements 
BooleanExpression {
+
+        String search;
+
+        public NotContainsExpression(Expression right, String search) {
+            super(right);
+            this.search = search;
+        }
+
+        public String getExpressionSymbol() {
+            return "NOT CONTAINS";
+        }
+
+        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).contains(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 createContains(Expression left, String 
search) {
+        return new ContainsExpression(left, search);
+    }
+
+    public static BooleanExpression createNotContains(Expression left, String 
search) {
+        return new NotContainsExpression(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/ParseException.java 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java
index 0a327bea1..39762509e 100644
--- a/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java
+++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/ParseException.java
@@ -202,4 +202,4 @@ public class ParseException extends Exception {
     }
 
 }
-/* JavaCC - OriginalChecksum=4c829b0daa2c9af00ddafe2441eb9097 (do not edit 
this line) */
+/* JavaCC - OriginalChecksum=60cf9c227a487e4be49599bc903f0a6a (do not edit 
this line) */
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 5658391fd..d23e6ee97 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
@@ -41,10 +41,10 @@ import java.util.ArrayList;
 public class SelectorParser implements SelectorParserConstants {
 
     private static final Cache<String, Object> PARSE_CACHE = 
CacheBuilder.newBuilder().maximumSize(100).build();
-    //    private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = 
"convert_string_expressions:";
+//    private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = 
"convert_string_expressions:";
 
     public static BooleanExpression parse(String sql) throws MQFilterException 
{
-        //        sql = "("+sql+")";
+//        sql = "("+sql+")";
         Object result = PARSE_CACHE.getIfPresent(sql);
         if (result instanceof MQFilterException) {
             throw (MQFilterException) result;
@@ -52,14 +52,14 @@ public class SelectorParser implements 
SelectorParserConstants {
             return (BooleanExpression) result;
         } else {
 
-            //            boolean convertStringExpressions = false;
-            //            if( 
sql.startsWith(CONVERT_STRING_EXPRESSIONS_PREFIX)) {
-            //                convertStringExpressions = true;
-            //                sql = 
sql.substring(CONVERT_STRING_EXPRESSIONS_PREFIX.length());
-            //            }
-            //            if( convertStringExpressions ) {
-            //                
ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true);
-            //            }
+//            boolean convertStringExpressions = false;
+//            if( sql.startsWith(CONVERT_STRING_EXPRESSIONS_PREFIX)) {
+//                convertStringExpressions = true;
+//                sql = 
sql.substring(CONVERT_STRING_EXPRESSIONS_PREFIX.length());
+//            }
+//            if( convertStringExpressions ) {
+//                ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true);
+//            }
             ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true);
             try {
 
@@ -71,9 +71,9 @@ public class SelectorParser implements 
SelectorParserConstants {
                 throw t;
             } finally {
                 ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove();
-                //                if( convertStringExpressions ) {
-                //                    
ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove();
-                //                }
+//                if( convertStringExpressions ) {
+//                    ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove();
+//                }
             }
         }
     }
@@ -114,8 +114,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         Expression left = null;
         left = orExpression();
         {
-            if (true)
-                return asBooleanExpression(left);
+            if (true) return asBooleanExpression(left);
         }
         throw new Error("Missing return statement in function");
     }
@@ -138,8 +137,7 @@ public class SelectorParser implements 
SelectorParserConstants {
             left = LogicExpression.createOR(asBooleanExpression(left), 
asBooleanExpression(right));
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -162,8 +160,7 @@ public class SelectorParser implements 
SelectorParserConstants {
             left = LogicExpression.createAND(asBooleanExpression(left), 
asBooleanExpression(right));
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -176,21 +173,21 @@ public class SelectorParser implements 
SelectorParserConstants {
         while (true) {
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
                 case IS:
-                case 22:
                 case 23:
+                case 24:
                     break;
                 default:
                     jjLa1[2] = jjGen;
                     break label_3;
             }
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 22:
-                    jj_consume_token(22);
+                case 23:
+                    jj_consume_token(23);
                     right = comparisonExpression();
                     left = ComparisonExpression.createEqual(left, right);
                     break;
-                case 23:
-                    jj_consume_token(23);
+                case 24:
+                    jj_consume_token(24);
                     right = comparisonExpression();
                     left = ComparisonExpression.createNotEqual(left, right);
                     break;
@@ -217,8 +214,7 @@ public class SelectorParser implements 
SelectorParserConstants {
             }
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -238,111 +234,127 @@ public class SelectorParser implements 
SelectorParserConstants {
                 case NOT:
                 case BETWEEN:
                 case IN:
-                case 24:
+                case CONTAINS:
                 case 25:
                 case 26:
                 case 27:
+                case 28:
                     break;
                 default:
                     jjLa1[5] = jjGen;
                     break label_4;
             }
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 24:
-                    jj_consume_token(24);
-                    right = unaryExpr();
-                    left = ComparisonExpression.createGreaterThan(left, right);
-                    break;
                 case 25:
                     jj_consume_token(25);
                     right = unaryExpr();
-                    left = ComparisonExpression.createGreaterThanEqual(left, 
right);
+                    left = ComparisonExpression.createGreaterThan(left, right);
                     break;
                 case 26:
                     jj_consume_token(26);
                     right = unaryExpr();
-                    left = ComparisonExpression.createLessThan(left, right);
+                    left = ComparisonExpression.createGreaterThanEqual(left, 
right);
                     break;
                 case 27:
                     jj_consume_token(27);
                     right = unaryExpr();
+                    left = ComparisonExpression.createLessThan(left, right);
+                    break;
+                case 28:
+                    jj_consume_token(28);
+                    right = unaryExpr();
                     left = ComparisonExpression.createLessThanEqual(left, 
right);
                     break;
-                case BETWEEN:
-                    jj_consume_token(BETWEEN);
-                    low = unaryExpr();
-                    jj_consume_token(AND);
-                    high = unaryExpr();
-                    left = ComparisonExpression.createBetween(left, low, high);
+                case CONTAINS:
+                    jj_consume_token(CONTAINS);
+                    t = stringLitteral();
+                    left = ComparisonExpression.createContains(left, t);
                     break;
                 default:
                     jjLa1[8] = jjGen;
                     if (jj_2_2(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(CONTAINS);
+                        t = stringLitteral();
+                        left = ComparisonExpression.createNotContains(left, t);
                     } else {
                         switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                            case IN:
-                                jj_consume_token(IN);
-                                jj_consume_token(28);
-                                t = stringLitteral();
-                                list = new ArrayList();
-                                list.add(t);
-                                label_5:
-                                while (true) {
-                                    switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                                        case 29:
-                                            break;
-                                        default:
-                                            jjLa1[6] = jjGen;
-                                            break label_5;
-                                    }
-                                    jj_consume_token(29);
-                                    t = stringLitteral();
-                                    list.add(t);
-                                }
-                                jj_consume_token(30);
-                                left = 
ComparisonExpression.createInFilter(left, list);
+                            case BETWEEN:
+                                jj_consume_token(BETWEEN);
+                                low = unaryExpr();
+                                jj_consume_token(AND);
+                                high = unaryExpr();
+                                left = 
ComparisonExpression.createBetween(left, low, high);
                                 break;
                             default:
                                 jjLa1[9] = jjGen;
                                 if (jj_2_3(2)) {
                                     jj_consume_token(NOT);
-                                    jj_consume_token(IN);
-                                    jj_consume_token(28);
-                                    t = stringLitteral();
-                                    list = new ArrayList();
-                                    list.add(t);
-                                    label_6:
-                                    while (true) {
-                                        switch ((jjNtk == -1) ? jj_ntk() : 
jjNtk) {
-                                            case 29:
-                                                break;
-                                            default:
-                                                jjLa1[7] = jjGen;
-                                                break label_6;
-                                        }
-                                        jj_consume_token(29);
-                                        t = stringLitteral();
-                                        list.add(t);
-                                    }
-                                    jj_consume_token(30);
-                                    left = 
ComparisonExpression.createNotInFilter(left, list);
+                                    jj_consume_token(BETWEEN);
+                                    low = unaryExpr();
+                                    jj_consume_token(AND);
+                                    high = unaryExpr();
+                                    left = 
ComparisonExpression.createNotBetween(left, low, high);
                                 } else {
-                                    jj_consume_token(-1);
-                                    throw new ParseException();
+                                    switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
+                                        case IN:
+                                            jj_consume_token(IN);
+                                            jj_consume_token(29);
+                                            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);
+                                            break;
+                                        default:
+                                            jjLa1[10] = jjGen;
+                                            if (jj_2_4(2)) {
+                                                jj_consume_token(NOT);
+                                                jj_consume_token(IN);
+                                                jj_consume_token(29);
+                                                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);
+                                            } else {
+                                                jj_consume_token(-1);
+                                                throw new ParseException();
+                                            }
+                                    }
                                 }
                         }
                     }
             }
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -350,13 +362,13 @@ public class SelectorParser implements 
SelectorParserConstants {
     final public Expression unaryExpr() throws ParseException {
         String s = null;
         Expression left = null;
-        if (jj_2_4(2147483647)) {
-            jj_consume_token(31);
+        if (jj_2_5(2147483647)) {
+            jj_consume_token(32);
             left = unaryExpr();
         } else {
             switch ((jjNtk == -1) ? jj_ntk() : jjNtk) {
-                case 32:
-                    jj_consume_token(32);
+                case 33:
+                    jj_consume_token(33);
                     left = unaryExpr();
                     left = UnaryExpression.createNegate(left);
                     break;
@@ -372,18 +384,17 @@ public class SelectorParser implements 
SelectorParserConstants {
                 case FLOATING_POINT_LITERAL:
                 case STRING_LITERAL:
                 case ID:
-                case 28:
+                case 29:
                     left = primaryExpr();
                     break;
                 default:
-                    jjLa1[10] = jjGen;
+                    jjLa1[11] = jjGen;
                     jj_consume_token(-1);
                     throw new ParseException();
             }
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -402,19 +413,18 @@ public class SelectorParser implements 
SelectorParserConstants {
             case ID:
                 left = variable();
                 break;
-            case 28:
-                jj_consume_token(28);
+            case 29:
+                jj_consume_token(29);
                 left = orExpression();
-                jj_consume_token(30);
+                jj_consume_token(31);
                 break;
             default:
-                jjLa1[11] = jjGen;
+                jjLa1[12] = jjGen;
                 jj_consume_token(-1);
                 throw new ParseException();
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -449,13 +459,12 @@ public class SelectorParser implements 
SelectorParserConstants {
                 left = BooleanConstantExpression.NULL;
                 break;
             default:
-                jjLa1[12] = jjGen;
+                jjLa1[13] = jjGen;
                 jj_consume_token(-1);
                 throw new ParseException();
         }
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -474,8 +483,7 @@ public class SelectorParser implements 
SelectorParserConstants {
             rc.append(c);
         }
         {
-            if (true)
-                return rc.toString();
+            if (true) return rc.toString();
         }
         throw new Error("Missing return statement in function");
     }
@@ -486,8 +494,7 @@ public class SelectorParser implements 
SelectorParserConstants {
         t = jj_consume_token(ID);
         left = new PropertyExpression(t.image);
         {
-            if (true)
-                return left;
+            if (true) return left;
         }
         throw new Error("Missing return statement in function");
     }
@@ -540,94 +547,26 @@ public class SelectorParser implements 
SelectorParserConstants {
         }
     }
 
-    private boolean jj_3R_7() {
-        Token xsp;
-        xsp = jjScanpos;
-        if (jj_3R_8()) {
-            jjScanpos = xsp;
-            if (jj_3R_9()) {
-                jjScanpos = xsp;
-                if (jj_3R_10()) {
-                    jjScanpos = xsp;
-                    if (jj_3R_11())
-                        return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean jj_3R_43() {
-        if (jj_scan_token(29))
-            return true;
-        if (jj_3R_27())
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_24() {
-        if (jj_scan_token(NULL))
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_35() {
-        if (jj_scan_token(IS))
-            return true;
-        if (jj_scan_token(NOT))
-            return true;
-        if (jj_scan_token(NULL))
-            return true;
-        return false;
-    }
-
-    private boolean jj_3_1() {
-        if (jj_scan_token(IS))
-            return true;
-        if (jj_scan_token(NULL))
+    private boolean jj_2_5(int xla) {
+        jjLa = xla;
+        jjLastpos = jjScanpos = token;
+        try {
+            return !jj_3_5();
+        } catch (LookaheadSuccess ls) {
             return true;
-        return false;
+        } finally {
+            jj_save(4, xla);
+        }
     }
 
-    private boolean jj_3R_23() {
-        if (jj_scan_token(FALSE))
-            return true;
+    private boolean jj_3R_21() {
+        if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
         return false;
     }
 
     private boolean jj_3R_34() {
-        if (jj_scan_token(23))
-            return true;
-        if (jj_3R_30())
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_22() {
-        if (jj_scan_token(TRUE))
-            return true;
-        return false;
-    }
-
-    private boolean jj_3_3() {
-        if (jj_scan_token(NOT))
-            return true;
-        if (jj_scan_token(IN))
-            return true;
-        if (jj_scan_token(28))
-            return true;
-        if (jj_3R_27())
-            return true;
-        Token xsp;
-        while (true) {
-            xsp = jjScanpos;
-            if (jj_3R_43()) {
-                jjScanpos = xsp;
-                break;
-            }
-        }
-        if (jj_scan_token(30))
-            return true;
+        if (jj_scan_token(24)) return true;
+        if (jj_3R_30()) return true;
         return false;
     }
 
@@ -640,8 +579,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                 jjScanpos = xsp;
                 if (jj_3_1()) {
                     jjScanpos = xsp;
-                    if (jj_3R_35())
-                        return true;
+                    if (jj_3R_35()) return true;
                 }
             }
         }
@@ -649,78 +587,47 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private boolean jj_3R_33() {
-        if (jj_scan_token(22))
-            return true;
-        if (jj_3R_30())
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_42() {
-        if (jj_scan_token(29))
-            return true;
-        if (jj_3R_27())
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_21() {
-        if (jj_scan_token(FLOATING_POINT_LITERAL))
-            return true;
+        if (jj_scan_token(23)) return true;
+        if (jj_3R_30()) return true;
         return false;
     }
 
     private boolean jj_3R_20() {
-        if (jj_scan_token(DECIMAL_LITERAL))
-            return true;
+        if (jj_scan_token(DECIMAL_LITERAL)) return true;
         return false;
     }
 
-    private boolean jj_3R_28() {
-        if (jj_3R_30())
-            return true;
+    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_31()) {
+            if (jj_3R_43()) {
                 jjScanpos = xsp;
                 break;
             }
         }
+        if (jj_scan_token(31)) return true;
         return false;
     }
 
-    private boolean jj_3R_41() {
-        if (jj_scan_token(IN))
-            return true;
-        if (jj_scan_token(28))
-            return true;
-        if (jj_3R_27())
-            return true;
+    private boolean jj_3R_19() {
+        if (jj_3R_27()) return true;
+        return false;
+    }
+
+    private boolean jj_3R_28() {
+        if (jj_3R_30()) return true;
         Token xsp;
         while (true) {
             xsp = jjScanpos;
-            if (jj_3R_42()) {
+            if (jj_3R_31()) {
                 jjScanpos = xsp;
                 break;
             }
         }
-        if (jj_scan_token(30))
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_19() {
-        if (jj_3R_27())
-            return true;
-        return false;
-    }
-
-    private boolean jj_3R_29() {
-        if (jj_scan_token(AND))
-            return true;
-        if (jj_3R_28())
-            return true;
         return false;
     }
 
@@ -737,8 +644,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                         jjScanpos = xsp;
                         if (jj_3R_23()) {
                             jjScanpos = xsp;
-                            if (jj_3R_24())
-                                return true;
+                            if (jj_3R_24()) return true;
                         }
                     }
                 }
@@ -747,35 +653,61 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
+    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;
+        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;
+        return false;
+    }
+
+    private boolean jj_3R_29() {
+        if (jj_scan_token(AND)) return true;
+        if (jj_3R_28()) return true;
+        return false;
+    }
+
     private boolean jj_3_2() {
-        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(NOT)) return true;
+        if (jj_scan_token(CONTAINS)) return true;
+        if (jj_3R_27()) return true;
+        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;
+        return false;
+    }
+
+    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_40() {
-        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(CONTAINS)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
     private boolean jj_3R_25() {
-        if (jj_3R_28())
-            return true;
+        if (jj_3R_28()) return true;
         Token xsp;
         while (true) {
             xsp = jjScanpos;
@@ -787,77 +719,62 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3R_39() {
-        if (jj_scan_token(27))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3R_17() {
+        if (jj_scan_token(ID)) return true;
         return false;
     }
 
-    private boolean jj_3R_15() {
-        if (jj_scan_token(28))
-            return true;
-        if (jj_3R_18())
-            return true;
-        if (jj_scan_token(30))
-            return true;
+    private boolean jj_3R_12() {
+        Token xsp;
+        xsp = jjScanpos;
+        if (jj_3R_13()) {
+            jjScanpos = xsp;
+            if (jj_3R_14()) {
+                jjScanpos = xsp;
+                if (jj_3R_15()) return true;
+            }
+        }
         return false;
     }
 
-    private boolean jj_3R_14() {
-        if (jj_3R_17())
-            return true;
+    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(26))
-            return true;
-        if (jj_3R_7())
-            return true;
+        if (jj_scan_token(27)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_13() {
-        if (jj_3R_16())
-            return true;
+    private boolean jj_3R_11() {
+        if (jj_3R_12()) return true;
         return false;
     }
 
     private boolean jj_3R_26() {
-        if (jj_scan_token(OR))
-            return true;
-        if (jj_3R_25())
-            return true;
+        if (jj_scan_token(OR)) return true;
+        if (jj_3R_25()) return true;
         return false;
     }
 
-    private boolean jj_3R_17() {
-        if (jj_scan_token(ID))
-            return true;
+    private boolean jj_3R_37() {
+        if (jj_scan_token(26)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_37() {
-        if (jj_scan_token(25))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3_5() {
+        if (jj_scan_token(32)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_12() {
-        Token xsp;
-        xsp = jjScanpos;
-        if (jj_3R_13()) {
-            jjScanpos = xsp;
-            if (jj_3R_14()) {
-                jjScanpos = xsp;
-                if (jj_3R_15())
-                    return true;
-            }
-        }
+    private boolean jj_3R_10() {
+        if (jj_scan_token(NOT)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
@@ -878,8 +795,13 @@ public class SelectorParser implements 
SelectorParserConstants {
                                 jjScanpos = xsp;
                                 if (jj_3R_41()) {
                                     jjScanpos = xsp;
-                                    if (jj_3_3())
-                                        return true;
+                                    if (jj_3_3()) {
+                                        jjScanpos = xsp;
+                                        if (jj_3R_42()) {
+                                            jjScanpos = xsp;
+                                            if (jj_3_4()) return true;
+                                        }
+                                    }
                                 }
                             }
                         }
@@ -891,22 +813,24 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private boolean jj_3R_36() {
-        if (jj_scan_token(24))
-            return true;
-        if (jj_3R_7())
-            return true;
+        if (jj_scan_token(25)) 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_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;
         return false;
     }
 
     private boolean jj_3R_18() {
-        if (jj_3R_25())
-            return true;
+        if (jj_3R_25()) return true;
         Token xsp;
         while (true) {
             xsp = jjScanpos;
@@ -918,55 +842,95 @@ public class SelectorParser implements 
SelectorParserConstants {
         return false;
     }
 
-    private boolean jj_3_4() {
-        if (jj_scan_token(31))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3R_8() {
+        if (jj_scan_token(32)) return true;
+        if (jj_3R_7()) return true;
         return false;
     }
 
-    private boolean jj_3R_10() {
-        if (jj_scan_token(NOT))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3R_7() {
+        Token xsp;
+        xsp = jjScanpos;
+        if (jj_3R_8()) {
+            jjScanpos = xsp;
+            if (jj_3R_9()) {
+                jjScanpos = xsp;
+                if (jj_3R_10()) {
+                    jjScanpos = xsp;
+                    if (jj_3R_11()) return true;
+                }
+            }
+        }
         return false;
     }
 
-    private boolean jj_3R_9() {
-        if (jj_scan_token(32))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3R_44() {
+        if (jj_scan_token(30)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
-    private boolean jj_3R_27() {
-        if (jj_scan_token(STRING_LITERAL))
-            return true;
+    private boolean jj_3R_30() {
+        if (jj_3R_7()) return true;
+        Token xsp;
+        while (true) {
+            xsp = jjScanpos;
+            if (jj_3R_32()) {
+                jjScanpos = xsp;
+                break;
+            }
+        }
         return false;
     }
 
-    private boolean jj_3R_30() {
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3R_24() {
+        if (jj_scan_token(NULL)) return true;
+        return false;
+    }
+
+    private boolean jj_3R_23() {
+        if (jj_scan_token(FALSE)) return true;
+        return false;
+    }
+
+    private boolean jj_3R_35() {
+        if (jj_scan_token(IS)) return true;
+        if (jj_scan_token(NOT)) return true;
+        if (jj_scan_token(NULL)) return true;
+        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;
+        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_32()) {
+            if (jj_3R_44()) {
                 jjScanpos = xsp;
                 break;
             }
         }
+        if (jj_scan_token(31)) return true;
         return false;
     }
 
-    private boolean jj_3R_8() {
-        if (jj_scan_token(31))
-            return true;
-        if (jj_3R_7())
-            return true;
+    private boolean jj_3_1() {
+        if (jj_scan_token(IS)) return true;
+        if (jj_scan_token(NULL)) return true;
+        return false;
+    }
+
+    private boolean jj_3R_43() {
+        if (jj_scan_token(30)) return true;
+        if (jj_3R_27()) return true;
         return false;
     }
 
@@ -987,7 +951,7 @@ public class SelectorParser implements 
SelectorParserConstants {
     private Token jjScanpos, jjLastpos;
     private int jjLa;
     private int jjGen;
-    final private int[] jjLa1 = new int[13];
+    final private int[] jjLa1 = new int[14];
     static private int[] jjLa10;
     static private int[] jjLa11;
 
@@ -997,16 +961,14 @@ public class SelectorParser implements 
SelectorParserConstants {
     }
 
     private static void jj_la1_init_0() {
-        jjLa10 = new int[] {
-            0x400, 0x200, 0xc10000, 0xc00000, 0x10000, 0xf001900, 0x20000000, 
0x20000000, 0xf000800,
-            0x1000, 0x1036e100, 0x1036e000, 0x16e000};
+        jjLa10 = new int[]{0x400, 0x200, 0x1810000, 0x1800000, 0x10000, 
0x1e021900, 0x40000000, 0x40000000, 0x1e020000, 0x800, 0x1000, 0x206ce100, 
0x206ce000, 0x2ce000,};
     }
 
     private static void jj_la1_init_1() {
-        jjLa11 = new int[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x1, 0x0, 0x0};
+        jjLa11 = new int[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x2, 0x0, 0x0,};
     }
 
-    final private JJCalls[] jj2Rtns = new JJCalls[4];
+    final private JJCalls[] jj2Rtns = new JJCalls[5];
     private boolean jjRescan = false;
     private int jjGc = 0;
 
@@ -1030,10 +992,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     /**
@@ -1056,10 +1016,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     /**
@@ -1071,10 +1029,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     /**
@@ -1086,10 +1042,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     /**
@@ -1100,10 +1054,8 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     /**
@@ -1114,18 +1066,14 @@ public class SelectorParser implements 
SelectorParserConstants {
         token = new Token();
         jjNtk = -1;
         jjGen = 0;
-        for (int i = 0; i < 13; i++)
-            jjLa1[i] = -1;
-        for (int i = 0; i < jj2Rtns.length; i++)
-            jj2Rtns[i] = new JJCalls();
+        for (int i = 0; i < 14; i++) jjLa1[i] = -1;
+        for (int i = 0; i < jj2Rtns.length; i++) jj2Rtns[i] = new JJCalls();
     }
 
     private Token jj_consume_token(int kind) throws ParseException {
         Token oldToken;
-        if ((oldToken = token).next != null)
-            token = token.next;
-        else
-            token = token.next = tokenSource.getNextToken();
+        if ((oldToken = token).next != null) token = token.next;
+        else token = token.next = tokenSource.getNextToken();
         jjNtk = -1;
         if (token.kind == kind) {
             jjGen++;
@@ -1134,8 +1082,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                 for (int i = 0; i < jj2Rtns.length; i++) {
                     JJCalls c = jj2Rtns[i];
                     while (c != null) {
-                        if (c.gen < jjGen)
-                            c.first = null;
+                        if (c.gen < jjGen) c.first = null;
                         c = c.next;
                     }
                 }
@@ -1170,24 +1117,20 @@ public class SelectorParser implements 
SelectorParserConstants {
                 i++;
                 tok = tok.next;
             }
-            if (tok != null)
-                jj_add_error_token(kind, i);
+            if (tok != null) jj_add_error_token(kind, i);
         }
-        if (jjScanpos.kind != kind)
-            return true;
-        if (jjLa == 0 && jjScanpos == jjLastpos)
-            throw jjLs;
+        if (jjScanpos.kind != kind) return true;
+        if (jjLa == 0 && jjScanpos == jjLastpos) throw jjLs;
         return false;
     }
 
+
     /**
      * Get the next Token.
      */
     final public Token getNextToken() {
-        if (token.next != null)
-            token = token.next;
-        else
-            token = token.next = tokenSource.getNextToken();
+        if (token.next != null) token = token.next;
+        else token = token.next = tokenSource.getNextToken();
         jjNtk = -1;
         jjGen++;
         return token;
@@ -1199,10 +1142,8 @@ public class SelectorParser implements 
SelectorParserConstants {
     final public Token getToken(int index) {
         Token t = token;
         for (int i = 0; i < index; i++) {
-            if (t.next != null)
-                t = t.next;
-            else
-                t = t.next = tokenSource.getNextToken();
+            if (t.next != null) t = t.next;
+            else t = t.next = tokenSource.getNextToken();
         }
         return t;
     }
@@ -1221,8 +1162,7 @@ public class SelectorParser implements 
SelectorParserConstants {
     private int jjEndpos;
 
     private void jj_add_error_token(int kind, int pos) {
-        if (pos >= 100)
-            return;
+        if (pos >= 100) return;
         if (pos == jjEndpos + 1) {
             jjLasttokens[jjEndpos++] = kind;
         } else if (jjEndpos != 0) {
@@ -1243,8 +1183,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                     break jj_entries_loop;
                 }
             }
-            if (pos != 0)
-                jjLasttokens[(jjEndpos = pos) - 1] = kind;
+            if (pos != 0) jjLasttokens[(jjEndpos = pos) - 1] = kind;
         }
     }
 
@@ -1253,12 +1192,12 @@ public class SelectorParser implements 
SelectorParserConstants {
      */
     public ParseException generateParseException() {
         jjExpentries.clear();
-        boolean[] la1tokens = new boolean[33];
+        boolean[] la1tokens = new boolean[34];
         if (jjKind >= 0) {
             la1tokens[jjKind] = true;
             jjKind = -1;
         }
-        for (int i = 0; i < 13; i++) {
+        for (int i = 0; i < 14; i++) {
             if (jjLa1[i] == jjGen) {
                 for (int j = 0; j < 32; j++) {
                     if ((jjLa10[i] & (1 << j)) != 0) {
@@ -1270,7 +1209,7 @@ public class SelectorParser implements 
SelectorParserConstants {
                 }
             }
         }
-        for (int i = 0; i < 33; i++) {
+        for (int i = 0; i < 34; i++) {
             if (la1tokens[i]) {
                 jjExpentry = new int[1];
                 jjExpentry[0] = i;
@@ -1301,7 +1240,7 @@ public class SelectorParser implements 
SelectorParserConstants {
 
     private void jj_rescan_token() {
         jjRescan = true;
-        for (int i = 0; i < 4; i++) {
+        for (int i = 0; i < 5; i++) {
             try {
                 JJCalls p = jj2Rtns[i];
                 do {
@@ -1321,11 +1260,13 @@ public class SelectorParser implements 
SelectorParserConstants {
                             case 3:
                                 jj_3_4();
                                 break;
+                            case 4:
+                                jj_3_5();
+                                break;
                         }
                     }
                     p = p.next;
-                }
-                while (p != null);
+                } while (p != null);
             } catch (LookaheadSuccess ls) {
             }
         }
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 adb485143..09e03d9bb 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
@@ -53,7 +53,6 @@ import org.apache.rocketmq.filter.expression.LogicExpression;
 import org.apache.rocketmq.filter.expression.MQFilterException;
 import org.apache.rocketmq.filter.expression.PropertyExpression;
 import org.apache.rocketmq.filter.expression.UnaryExpression;
-import org.apache.rocketmq.filter.util.LRUCache;
 
 import java.io.StringReader;
 import java.util.ArrayList;
@@ -170,6 +169,7 @@ TOKEN [IGNORE_CASE] :
   | <  FALSE   : "FALSE" >
   | <  NULL    : "NULL" >
   | <  IS      : "IS" >
+  | <  CONTAINS    : "CONTAINS">
 }
 
 /* Literals */
@@ -322,6 +322,17 @@ Expression comparisonExpression() :
                 {
                     left = ComparisonExpression.createLessThanEqual(left, 
right);
                 }
+            |
+                <CONTAINS> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createContains(left, t);
+                }
+            |
+                LOOKAHEAD(2)
+                <NOT> <CONTAINS> t = stringLitteral()
+                {
+                    left = ComparisonExpression.createNotContains(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 915658ca6..8f849cb51 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
@@ -75,23 +75,27 @@ public interface SelectorParserConstants {
     /**
      * RegularExpression Id.
      */
-    int DECIMAL_LITERAL = 17;
+    int CONTAINS = 17;
     /**
      * RegularExpression Id.
      */
-    int FLOATING_POINT_LITERAL = 18;
+    int DECIMAL_LITERAL = 18;
     /**
      * RegularExpression Id.
      */
-    int EXPONENT = 19;
+    int FLOATING_POINT_LITERAL = 19;
     /**
      * RegularExpression Id.
      */
-    int STRING_LITERAL = 20;
+    int EXPONENT = 20;
     /**
      * RegularExpression Id.
      */
-    int ID = 21;
+    int STRING_LITERAL = 21;
+    /**
+     * RegularExpression Id.
+     */
+    int ID = 22;
 
     /**
      * Lexical state.
@@ -119,6 +123,7 @@ public interface SelectorParserConstants {
         "\"FALSE\"",
         "\"NULL\"",
         "\"IS\"",
+        "\"CONTAINS\"",
         "<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 b5bac9824..9d42eea71 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,33 +59,35 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 jjmatchedKind = 1;
                 return jjMoveNfa_0(5, 0);
             case 40:
-                jjmatchedKind = 28;
+                jjmatchedKind = 29;
                 return jjMoveNfa_0(5, 0);
             case 41:
-                jjmatchedKind = 30;
+                jjmatchedKind = 31;
                 return jjMoveNfa_0(5, 0);
             case 43:
-                jjmatchedKind = 31;
+                jjmatchedKind = 32;
                 return jjMoveNfa_0(5, 0);
             case 44:
-                jjmatchedKind = 29;
+                jjmatchedKind = 30;
                 return jjMoveNfa_0(5, 0);
             case 45:
-                jjmatchedKind = 32;
+                jjmatchedKind = 33;
                 return jjMoveNfa_0(5, 0);
             case 60:
-                jjmatchedKind = 26;
-                return jjMoveStringLiteralDfa1_0(0x8800000L);
+                jjmatchedKind = 27;
+                return jjMoveStringLiteralDfa1_0(0x11000000L);
             case 61:
-                jjmatchedKind = 22;
+                jjmatchedKind = 23;
                 return jjMoveNfa_0(5, 0);
             case 62:
-                jjmatchedKind = 24;
-                return jjMoveStringLiteralDfa1_0(0x2000000L);
+                jjmatchedKind = 25;
+                return jjMoveStringLiteralDfa1_0(0x4000000L);
             case 65:
                 return jjMoveStringLiteralDfa1_0(0x200L);
             case 66:
                 return jjMoveStringLiteralDfa1_0(0x800L);
+            case 67:
+                return jjMoveStringLiteralDfa1_0(0x20000L);
             case 70:
                 return jjMoveStringLiteralDfa1_0(0x4000L);
             case 73:
@@ -100,6 +102,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 return jjMoveStringLiteralDfa1_0(0x200L);
             case 98:
                 return jjMoveStringLiteralDfa1_0(0x800L);
+            case 99:
+                return jjMoveStringLiteralDfa1_0(0x20000L);
             case 102:
                 return jjMoveStringLiteralDfa1_0(0x4000L);
             case 105:
@@ -123,17 +127,17 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         }
         switch (curChar) {
             case 61:
-                if ((active0 & 0x2000000L) != 0L) {
-                    jjmatchedKind = 25;
+                if ((active0 & 0x4000000L) != 0L) {
+                    jjmatchedKind = 26;
                     jjmatchedPos = 1;
-                } else if ((active0 & 0x8000000L) != 0L) {
-                    jjmatchedKind = 27;
+                } else if ((active0 & 0x10000000L) != 0L) {
+                    jjmatchedKind = 28;
                     jjmatchedPos = 1;
                 }
                 break;
             case 62:
-                if ((active0 & 0x800000L) != 0L) {
-                    jjmatchedKind = 23;
+                if ((active0 & 0x1000000L) != 0L) {
+                    jjmatchedKind = 24;
                     jjmatchedPos = 1;
                 }
                 break;
@@ -148,7 +152,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 }
                 return jjMoveStringLiteralDfa2_0(active0, 0x200L);
             case 79:
-                return jjMoveStringLiteralDfa2_0(active0, 0x100L);
+                return jjMoveStringLiteralDfa2_0(active0, 0x20100L);
             case 82:
                 if ((active0 & 0x400L) != 0L) {
                     jjmatchedKind = 10;
@@ -174,7 +178,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 }
                 return jjMoveStringLiteralDfa2_0(active0, 0x200L);
             case 111:
-                return jjMoveStringLiteralDfa2_0(active0, 0x100L);
+                return jjMoveStringLiteralDfa2_0(active0, 0x20100L);
             case 114:
                 if ((active0 & 0x400L) != 0L) {
                     jjmatchedKind = 10;
@@ -212,6 +216,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 break;
             case 76:
                 return jjMoveStringLiteralDfa3_0(active0, 0xc000L);
+            case 78:
+                return jjMoveStringLiteralDfa3_0(active0, 0x20000L);
             case 84:
                 if ((active0 & 0x100L) != 0L) {
                     jjmatchedKind = 8;
@@ -228,6 +234,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 break;
             case 108:
                 return jjMoveStringLiteralDfa3_0(active0, 0xc000L);
+            case 110:
+                return jjMoveStringLiteralDfa3_0(active0, 0x20000L);
             case 116:
                 if ((active0 & 0x100L) != 0L) {
                     jjmatchedKind = 8;
@@ -265,6 +273,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 break;
             case 83:
                 return jjMoveStringLiteralDfa4_0(active0, 0x4000L);
+            case 84:
+                return jjMoveStringLiteralDfa4_0(active0, 0x20000L);
             case 87:
                 return jjMoveStringLiteralDfa4_0(active0, 0x800L);
             case 101:
@@ -281,6 +291,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                 break;
             case 115:
                 return jjMoveStringLiteralDfa4_0(active0, 0x4000L);
+            case 116:
+                return jjMoveStringLiteralDfa4_0(active0, 0x20000L);
             case 119:
                 return jjMoveStringLiteralDfa4_0(active0, 0x800L);
             default:
@@ -298,12 +310,16 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
             return jjMoveNfa_0(5, 3);
         }
         switch (curChar) {
+            case 65:
+                return jjMoveStringLiteralDfa5_0(active0, 0x20000L);
             case 69:
                 if ((active0 & 0x4000L) != 0L) {
                     jjmatchedKind = 14;
                     jjmatchedPos = 4;
                 }
                 return jjMoveStringLiteralDfa5_0(active0, 0x800L);
+            case 97:
+                return jjMoveStringLiteralDfa5_0(active0, 0x20000L);
             case 101:
                 if ((active0 & 0x4000L) != 0L) {
                     jjmatchedKind = 14;
@@ -327,8 +343,12 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         switch (curChar) {
             case 69:
                 return jjMoveStringLiteralDfa6_0(active0, 0x800L);
+            case 73:
+                return jjMoveStringLiteralDfa6_0(active0, 0x20000L);
             case 101:
                 return jjMoveStringLiteralDfa6_0(active0, 0x800L);
+            case 105:
+                return jjMoveStringLiteralDfa6_0(active0, 0x20000L);
             default:
                 break;
         }
@@ -349,19 +369,46 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                     jjmatchedKind = 11;
                     jjmatchedPos = 6;
                 }
-                break;
+                return jjMoveStringLiteralDfa7_0(active0, 0x20000L);
             case 110:
                 if ((active0 & 0x800L) != 0L) {
                     jjmatchedKind = 11;
                     jjmatchedPos = 6;
                 }
-                break;
+                return jjMoveStringLiteralDfa7_0(active0, 0x20000L);
             default:
                 break;
         }
         return jjMoveNfa_0(5, 6);
     }
 
+    private int jjMoveStringLiteralDfa7_0(long old0, long active0) {
+        if (((active0 &= old0)) == 0L)
+            return jjMoveNfa_0(5, 6);
+        try {
+            curChar = inputStream.readChar();
+        } catch (java.io.IOException e) {
+            return jjMoveNfa_0(5, 6);
+        }
+        switch (curChar) {
+            case 83:
+                if ((active0 & 0x20000L) != 0L) {
+                    jjmatchedKind = 17;
+                    jjmatchedPos = 7;
+                }
+                break;
+            case 115:
+                if ((active0 & 0x20000L) != 0L) {
+                    jjmatchedKind = 17;
+                    jjmatchedPos = 7;
+                }
+                break;
+            default:
+                break;
+        }
+        return jjMoveNfa_0(5, 7);
+    }
+
     static final long[] JJ_BIT_VEC_0 = {
         0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 
0xffffffffffffffffL
     };
@@ -396,8 +443,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             if ((0x3ff000000000000L & l) != 0L)
                                 jjCheckNAddStates(0, 3);
                             else if (curChar == 36) {
-                                if (kind > 21)
-                                    kind = 21;
+                                if (kind > 22)
+                                    kind = 22;
                                 jjCheckNAdd(28);
                             } else if (curChar == 39)
                                 jjCheckNAddStates(4, 6);
@@ -408,12 +455,12 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             else if (curChar == 45)
                                 jjstateSet[jjnewStateCnt++] = 0;
                             if ((0x3fe000000000000L & l) != 0L) {
-                                if (kind > 17)
-                                    kind = 17;
+                                if (kind > 18)
+                                    kind = 18;
                                 jjCheckNAddTwoStates(15, 16);
                             } else if (curChar == 48) {
-                                if (kind > 17)
-                                    kind = 17;
+                                if (kind > 18)
+                                    kind = 18;
                             }
                             break;
                         case 0:
@@ -465,21 +512,21 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                                 jjstateSet[jjnewStateCnt++] = 6;
                             break;
                         case 13:
-                            if (curChar == 48 && kind > 17)
-                                kind = 17;
+                            if (curChar == 48 && kind > 18)
+                                kind = 18;
                             break;
                         case 14:
                             if ((0x3fe000000000000L & l) == 0L)
                                 break;
-                            if (kind > 17)
-                                kind = 17;
+                            if (kind > 18)
+                                kind = 18;
                             jjCheckNAddTwoStates(15, 16);
                             break;
                         case 15:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 17)
-                                kind = 17;
+                            if (kind > 18)
+                                kind = 18;
                             jjCheckNAddTwoStates(15, 16);
                             break;
                         case 17:
@@ -489,8 +536,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 18:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAddTwoStates(18, 19);
                             break;
                         case 20:
@@ -500,8 +547,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 21:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAdd(21);
                             break;
                         case 22:
@@ -518,21 +565,21 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                                 jjCheckNAddStates(4, 6);
                             break;
                         case 26:
-                            if (curChar == 39 && kind > 20)
-                                kind = 20;
+                            if (curChar == 39 && kind > 21)
+                                kind = 21;
                             break;
                         case 27:
                             if (curChar != 36)
                                 break;
-                            if (kind > 21)
-                                kind = 21;
+                            if (kind > 22)
+                                kind = 22;
                             jjCheckNAdd(28);
                             break;
                         case 28:
                             if ((0x3ff001000000000L & l) == 0L)
                                 break;
-                            if (kind > 21)
-                                kind = 21;
+                            if (kind > 22)
+                                kind = 22;
                             jjCheckNAdd(28);
                             break;
                         case 29:
@@ -546,15 +593,15 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 31:
                             if (curChar != 46)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAddTwoStates(32, 33);
                             break;
                         case 32:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAddTwoStates(32, 33);
                             break;
                         case 34:
@@ -564,8 +611,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 35:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAdd(35);
                             break;
                         case 36:
@@ -579,15 +626,14 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 39:
                             if ((0x3ff000000000000L & l) == 0L)
                                 break;
-                            if (kind > 18)
-                                kind = 18;
+                            if (kind > 19)
+                                kind = 19;
                             jjCheckNAdd(39);
                             break;
                         default:
                             break;
                     }
-                }
-                while (i != startsAt);
+                } while (i != startsAt);
             } else if (curChar < 128) {
                 long l = 1L << (curChar & 077);
                 do {
@@ -596,8 +642,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         case 28:
                             if ((0x7fffffe87fffffeL & l) == 0L)
                                 break;
-                            if (kind > 21)
-                                kind = 21;
+                            if (kind > 22)
+                                kind = 22;
                             jjCheckNAdd(28);
                             break;
                         case 1:
@@ -611,8 +657,8 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                             jjCheckNAddTwoStates(10, 8);
                             break;
                         case 16:
-                            if ((0x100000001000L & l) != 0L && kind > 17)
-                                kind = 17;
+                            if ((0x100000001000L & l) != 0L && kind > 18)
+                                kind = 18;
                             break;
                         case 19:
                             if ((0x2000000020L & l) != 0L)
@@ -632,8 +678,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         default:
                             break;
                     }
-                }
-                while (i != startsAt);
+                } while (i != startsAt);
             } else {
                 int hiByte = (int) (curChar >> 8);
                 int i1 = hiByte >> 6;
@@ -662,8 +707,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
                         default:
                             break;
                     }
-                }
-                while (i != startsAt);
+                } while (i != startsAt);
             }
             if (kind != 0x7fffffff) {
                 jjmatchedKind = kind;
@@ -722,8 +766,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, "\75", "\74\76", 
"\76",
-        "\76\75", "\74", "\74\75", "\50", "\54", "\51", "\53", "\55"};
+        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.
@@ -732,7 +776,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
         "DEFAULT",
     };
     static final long[] JJ_TO_TOKEN = {
-        0x1fff7ff01L,
+        0x3ffefff01L,
     };
     static final long[] JJ_TO_SKIP = {
         0xfeL,
@@ -905,8 +949,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
     private void jjAddStates(int start, int end) {
         do {
             jjstateSet[jjnewStateCnt++] = JJ_NEXT_STATES[start];
-        }
-        while (start++ != end);
+        } while (start++ != end);
     }
 
     private void jjCheckNAddTwoStates(int state1, int state2) {
@@ -917,8 +960,7 @@ public class SelectorParserTokenManager implements 
SelectorParserConstants {
     private void jjCheckNAddStates(int start, int end) {
         do {
             jjCheckNAdd(JJ_NEXT_STATES[start]);
-        }
-        while (start++ != end);
+        } while (start++ != end);
     }
 
 }
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java
index 42626f0f2..b8e375e51 100644
--- 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java
+++ 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java
@@ -501,4 +501,4 @@ public class SimpleCharStream {
     }
 
 }
-/* JavaCC - OriginalChecksum=af79bfe4b18b4b4ea9720ffeb7e52fc5 (do not edit 
this line) */
+/* JavaCC - OriginalChecksum=ea3493f692d4975c1ad70c4a750107d3 (do not edit 
this line) */
diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java
index 8e6a48a08..edb788008 100644
--- a/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java
+++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/Token.java
@@ -149,4 +149,4 @@ public class Token implements java.io.Serializable {
     }
 
 }
-/* JavaCC - OriginalChecksum=6b0af88eb45a551d929d3cdd9582f827 (do not edit 
this line) */
+/* JavaCC - OriginalChecksum=20094f1ccfbf423c6d9e770d6a7a0188 (do not edit 
this line) */
diff --git 
a/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java 
b/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java
index 0aeb27cf4..4a8f2c86a 100644
--- a/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java
+++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/TokenMgrError.java
@@ -172,4 +172,4 @@ public class TokenMgrError extends Error {
         this(LexicalError(eofSeen, lexState, errorLine, errorColumn, 
errorAfter, curChar), reason);
     }
 }
-/* JavaCC - OriginalChecksum=e960778c8dcd73e167ed5bfddd59f288 (do not edit 
this line) */
+/* JavaCC - OriginalChecksum=de79709675790dcbad2e0d728aa630d1 (do not edit 
this line) */
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 8b02a2627..fa6b04af4 100644
--- a/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java
+++ b/filter/src/test/java/org/apache/rocketmq/filter/ExpressionTest.java
@@ -46,6 +46,324 @@ public class ExpressionTest {
     private static String nullOrExpression = "a is null OR a='hello'";
     private static String stringHasString = "TAGS is not null and 
TAGS='''''tag'''''";
 
+
+    @Test
+    public void testConstains_has() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "axb")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void test_notConstains_has() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "axb")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_has_not() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "abb")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_has_not() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "abb")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void testConstains_hasEmpty() throws Exception {
+        Expression expr = genExp("value contains ''");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "axb")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_hasEmpty() throws Exception {
+        Expression expr = genExp("value not contains ''");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", "axb")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_null_has_1() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", null)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_null_has_1() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", null)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_null_has_2() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+//                KeyValue.c("value", null)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_null_has_2() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+//                KeyValue.c("value", null)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_number_has() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", 1.23)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_number_has() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", 1.23)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_boolean_has() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", Boolean.TRUE)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_boolean_has() throws Exception {
+        Expression expr = genExp("value not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", Boolean.TRUE)
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_object_has() throws Exception {
+        Expression expr = genExp("value contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("value", new Object())
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_has_not_string_1() throws Exception {
+        try {
+            Expression expr = genExp("value contains x");  // will throw parse 
exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("value", "axb")
+            );
+            eval(expr, context, Boolean.FALSE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void test_notConstains_has_not_string_1() throws Exception {
+        try {
+            Expression expr = genExp("value not contains x");  // will throw 
parse exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("value", "axb")
+            );
+            eval(expr, context, Boolean.FALSE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void testConstains_has_not_string_2() throws Exception {
+        try {
+            Expression expr = genExp("value contains 123");  // will throw 
parse exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("value", "axb")
+            );
+            eval(expr, context, Boolean.FALSE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void test_notConstains_has_not_string_2() throws Exception {
+        try {
+            Expression expr = genExp("value not contains 123");  // will throw 
parse exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("value", "axb")
+            );
+            eval(expr, context, Boolean.FALSE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void testConstains_string_has_string() throws Exception {
+        Expression expr = genExp("'axb' contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void test_notConstains_string_has_string() throws Exception {
+        Expression expr = genExp("'axb' not contains 'x'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_string_has_not_string() throws Exception {
+        Expression expr = genExp("'axb' contains 'u'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_string_has_not_string() throws Exception {
+        Expression expr = genExp("'axb' not contains 'u'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void testConstains_string_has_empty() throws Exception {
+        Expression expr = genExp("'axb' contains ''");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_string_has_empty() throws Exception {
+        Expression expr = genExp("'axb' not contains ''");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_string_has_space() throws Exception {
+        Expression expr = genExp("' ' contains ' '");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void test_notConstains_string_has_space() throws Exception {
+        Expression expr = genExp("' ' not contains ' '");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testConstains_string_has_nothing() throws Exception {
+        try {
+            Expression expr = genExp("'axb' contains ");  // will throw parse 
exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("whatever", "whatever")
+            );
+            eval(expr, context, Boolean.TRUE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void test_notConstains_string_has_nothing() throws Exception {
+        try {
+            Expression expr = genExp("'axb' not contains ");  // will throw 
parse exception.
+            EvaluationContext context = genContext(
+                    KeyValue.c("whatever", "whatever")
+            );
+            eval(expr, context, Boolean.TRUE);
+        } catch (Throwable e) {
+        }
+    }
+
+    @Test
+    public void testConstains_string_has_special_1() throws Exception {
+        Expression expr = genExp("'axb' contains '.'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void test_notConstains_string_has_special_1() throws Exception {
+        Expression expr = genExp("'axb' not contains '.'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.TRUE);
+    }
+
+    @Test
+    public void testConstains_string_has_special_2() throws Exception {
+        Expression expr = genExp("'s' contains '\\'");
+        EvaluationContext context = genContext(
+                KeyValue.c("whatever", "whatever")
+        );
+        eval(expr, context, Boolean.FALSE);
+    }
+
+    @Test
+    public void testContainsAllInOne() throws Exception {
+        Expression expr = genExp("a not in ('4', '4', '5') and b between 3 and 
10 and c not contains '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);
diff --git a/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java 
b/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java
index 7dc2ab254..9e6291ff1 100644
--- a/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java
+++ b/filter/src/test/java/org/apache/rocketmq/filter/ParserTest.java
@@ -37,7 +37,7 @@ public class ParserTest {
     private static String equalNullExpression = "a is null";
     private static String notEqualNullExpression = "a is not null";
     private static String nowExpression = "a <= now";
-
+    private static String containsExpression = "a=3 and b contains 'xxx' and c 
not contains 'xxx'";
     private static String invalidExpression = "a and between 2 and 10";
     private static String illegalBetween = " a between 10 and 0";
 
@@ -45,7 +45,7 @@ public class ParserTest {
     public void testParse_valid() {
         for (String expr : Arrays.asList(
             andExpression, orExpression, inExpression, notInExpression, 
betweenExpression,
-            equalNullExpression, notEqualNullExpression, nowExpression
+            equalNullExpression, notEqualNullExpression, nowExpression, 
containsExpression
         )) {
 
             try {

Reply via email to