You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ch...@apache.org on 2021/08/12 09:26:51 UTC

[iotdb] branch master updated: [IOTDB-1536]Support fuzzy query (#3649)

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

chaow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 9d9138d  [IOTDB-1536]Support fuzzy query (#3649)
9d9138d is described below

commit 9d9138d413b4d3805eca34bbc6f6eadbdad596b1
Author: Hang Ji <55...@users.noreply.github.com>
AuthorDate: Thu Aug 12 17:26:27 2021 +0800

    [IOTDB-1536]Support fuzzy query (#3649)
    
    
    Co-authored-by: Jackie Tien <Ja...@foxmail.com>
    Co-authored-by: Xiangwei Wei <34...@users.noreply.github.com>
---
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4   |   1 +
 docs/UserGuide/Appendix/SQL-Reference.md           |  18 +++
 docs/zh/UserGuide/Appendix/SQL-Reference.md        |  17 +++
 .../iotdb/db/qp/constant/FilterConstant.java       |   6 +-
 .../iotdb/db/qp/logical/crud/LikeOperator.java     | 130 +++++++++++++++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |  13 +-
 .../iotdb/db/integration/IoTDBQueryDemoIT.java     | 158 +++++++++++++++++++++
 .../iotdb/db/qp/logical/LogicalPlanSmallTest.java  |  14 ++
 .../iotdb/db/qp/physical/PhysicalPlanTest.java     |  17 +++
 .../iotdb/tsfile/read/filter/ValueFilter.java      |  27 ++++
 .../read/filter/factory/FilterSerializeId.java     |   3 +-
 .../iotdb/tsfile/read/filter/operator/Like.java    | 114 +++++++++++++++
 12 files changed, 515 insertions(+), 3 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
index d122f24..d697dec 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
@@ -211,6 +211,7 @@ predicate
     : (TIME | TIMESTAMP | suffixPath | fullPath) comparisonOperator constant
     | (TIME | TIMESTAMP | suffixPath | fullPath) inClause
     | OPERATOR_NOT? LR_BRACKET orExpression RR_BRACKET
+    | (suffixPath | fullPath) LIKE stringLiteral
     ;
 
 inClause
diff --git a/docs/UserGuide/Appendix/SQL-Reference.md b/docs/UserGuide/Appendix/SQL-Reference.md
index 77b9e65..33a8081 100644
--- a/docs/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/UserGuide/Appendix/SQL-Reference.md
@@ -716,6 +716,24 @@ In this situation, it will throws an exception if * corresponds to multiple sens
 
 ```
 
+* Like Statement
+
+Fuzzy query only supports regular expressions with data type of text and Java standard library style when matching
+```
+SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause>
+Select Clause : <Path> [COMMA <Path>]*
+FromClause : < PrefixPath > [COMMA < PrefixPath >]*
+WhereClause : andExpression (OPERATOR_OR andExpression)*
+andExpression : predicate (OPERATOR_AND predicate)*
+predicate : (suffixPath | fullPath) LIKE stringLiteral
+stringLiteral : SINGLE_QUOTE_STRING_LITERAL | DOUBLE_QUOTE_STRING_LITERAL
+
+Eg. select s1 from root.sg.d1 where s1 like 'Regex'
+Eg. select s1, s2 FROM root.sg.d1 where s1 like 'regex' and s2 like 'Regex'
+Eg. select * from root.sg.d1 where s1 like 'Regex'
+Eg. select * from root.sg.d1 where s1 like 'Regex' and time > 100
+```
+
 ## Database Management Statement
 
 * Create User
diff --git a/docs/zh/UserGuide/Appendix/SQL-Reference.md b/docs/zh/UserGuide/Appendix/SQL-Reference.md
index 8abcf5a..ceaa38c 100644
--- a/docs/zh/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/zh/UserGuide/Appendix/SQL-Reference.md
@@ -705,6 +705,23 @@ E.g. select * as temperature from root.sg.d1
 这种情况如果 * 匹配多个传感器,则无法正常显示。
 
 ```
+* Like 语句
+
+模糊查询,仅支持数据类型为 TEXT,匹配时为 Java 标准库风格的正则表达式
+```
+SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause>
+Select Clause : <Path> [COMMA <Path>]*
+FromClause : < PrefixPath > [COMMA < PrefixPath >]*
+WhereClause : andExpression (OPERATOR_OR andExpression)*
+andExpression : predicate (OPERATOR_AND predicate)*
+predicate : (suffixPath | fullPath) LIKE stringLiteral
+stringLiteral : SINGLE_QUOTE_STRING_LITERAL | DOUBLE_QUOTE_STRING_LITERAL
+
+Eg. select s1 from root.sg.d1 where s1 like 'Regex'
+Eg. select s1, s2 FROM root.sg.d1 where s1 like 'regex' and s2 like 'Regex'
+Eg. select * from root.sg.d1 where s1 like 'Regex'
+Eg. select * from root.sg.d1 where s1 like 'Regex' and time > 100
+```
 
 ## 数据库管理语句
 
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/constant/FilterConstant.java b/server/src/main/java/org/apache/iotdb/db/qp/constant/FilterConstant.java
index 2920594..d90dc16 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/FilterConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/FilterConstant.java
@@ -44,7 +44,8 @@ public class FilterConstant {
     LESSTHAN,
     GREATERTHANOREQUALTO,
     GREATERTHAN,
-    IN
+    IN,
+    LIKE
   }
 
   static {
@@ -55,6 +56,7 @@ public class FilterConstant {
     lexerToFilterType.put(SqlBaseLexer.OPERATOR_GTE, FilterType.GREATERTHANOREQUALTO);
     lexerToFilterType.put(SqlBaseLexer.OPERATOR_GT, FilterType.GREATERTHAN);
     lexerToFilterType.put(SqlBaseLexer.OPERATOR_IN, FilterType.IN);
+    lexerToFilterType.put(SqlBaseLexer.LIKE, FilterType.LIKE);
   }
 
   static {
@@ -67,6 +69,7 @@ public class FilterConstant {
     filterSymbol.put(FilterType.LESSTHAN, "<");
     filterSymbol.put(FilterType.GREATERTHANOREQUALTO, ">=");
     filterSymbol.put(FilterType.GREATERTHAN, ">");
+    filterSymbol.put(FilterType.LIKE, "%");
   }
 
   static {
@@ -80,6 +83,7 @@ public class FilterConstant {
     filterNames.put(FilterType.GREATERTHANOREQUALTO, "greaterthan_or_equalto");
     filterNames.put(FilterType.GREATERTHAN, "greaterthan");
     filterNames.put(FilterType.IN, "in");
+    filterNames.put(FilterType.LIKE, "like");
   }
 
   static {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/LikeOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/LikeOperator.java
new file mode 100644
index 0000000..10c9c6e
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/LikeOperator.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.qp.logical.crud;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOperatorException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.constant.FilterConstant.FilterType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.expression.IUnaryExpression;
+import org.apache.iotdb.tsfile.read.expression.impl.SingleSeriesExpression;
+import org.apache.iotdb.tsfile.read.filter.ValueFilter;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+import org.apache.iotdb.tsfile.utils.Pair;
+import org.apache.iotdb.tsfile.utils.StringContainer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.iotdb.tsfile.file.metadata.enums.TSDataType.TEXT;
+
+/** fuzzy query structure LikeOperator. */
+public class LikeOperator extends FunctionOperator {
+
+  private static final Logger logger = LoggerFactory.getLogger(LikeOperator.class);
+
+  protected String value;
+
+  public LikeOperator(FilterType filterType, PartialPath path, String value) {
+    super(filterType);
+    this.singlePath = path;
+    this.value = value;
+    isLeaf = true;
+    isSingle = true;
+  }
+
+  @Override
+  protected Pair<IUnaryExpression, String> transformToSingleQueryFilter(
+      Map<PartialPath, TSDataType> pathTSDataTypeHashMap)
+      throws LogicalOperatorException, MetadataException {
+    TSDataType type = pathTSDataTypeHashMap.get(singlePath);
+    if (type == null) {
+      throw new MetadataException(
+          "given seriesPath:{" + singlePath.getFullPath() + "} don't exist in metadata");
+    }
+    IUnaryExpression ret;
+    if (type != TEXT) {
+      throw new LogicalOperatorException(type.toString(), "Only TEXT is supported in 'Like'");
+    } else if (value.startsWith("\"") && value.endsWith("\"")) {
+      throw new LogicalOperatorException(value, "Please use single quotation marks");
+    } else {
+      ret =
+          Like.getUnaryExpression(
+              singlePath,
+              (value.startsWith("'") && value.endsWith("'"))
+                  ? value.substring(1, value.length() - 1)
+                  : value);
+    }
+    return new Pair<>(ret, singlePath.getFullPath());
+  }
+
+  private static class Like {
+    public static <T extends Comparable<T>> IUnaryExpression getUnaryExpression(
+        PartialPath path, String value) {
+      return new SingleSeriesExpression(path, ValueFilter.like(value));
+    }
+
+    public <T extends Comparable<T>> Filter getValueFilter(String value) {
+      return ValueFilter.like(value);
+    }
+  }
+
+  @Override
+  public String showTree(int spaceNum) {
+    StringContainer sc = new StringContainer();
+    for (int i = 0; i < spaceNum; i++) {
+      sc.addTail("  ");
+    }
+    sc.addTail(singlePath.getFullPath(), getFilterSymbol(), value, ", single\n");
+    return sc.toString();
+  }
+
+  @Override
+  public LikeOperator copy() {
+    LikeOperator ret =
+        new LikeOperator(this.filterType, new PartialPath(singlePath.getNodes().clone()), value);
+    ret.isLeaf = isLeaf;
+    ret.isSingle = isSingle;
+    ret.pathSet = pathSet;
+    return ret;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
+    LikeOperator that = (LikeOperator) o;
+    return Objects.equals(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(super.hashCode(), singlePath, value);
+  }
+
+  @Override
+  public String toString() {
+    return "[" + singlePath.getFullPath() + getFilterSymbol() + value + "]";
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index ef09d82..83d30ce 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -43,6 +43,7 @@ import org.apache.iotdb.db.qp.logical.crud.GroupByQueryOperator;
 import org.apache.iotdb.db.qp.logical.crud.InOperator;
 import org.apache.iotdb.db.qp.logical.crud.InsertOperator;
 import org.apache.iotdb.db.qp.logical.crud.LastQueryOperator;
+import org.apache.iotdb.db.qp.logical.crud.LikeOperator;
 import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
 import org.apache.iotdb.db.qp.logical.crud.SelectComponent;
 import org.apache.iotdb.db.qp.logical.crud.SelectIntoOperator;
@@ -2035,14 +2036,24 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
 
   @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
   private FilterOperator parsePredicate(PredicateContext ctx) {
+    PartialPath path = null;
     if (ctx.OPERATOR_NOT() != null) {
       FilterOperator notOp = new FilterOperator(FilterType.KW_NOT);
       notOp.addChildOperator(parseOrExpression(ctx.orExpression()));
       return notOp;
     } else if (ctx.LR_BRACKET() != null && ctx.OPERATOR_NOT() == null) {
       return parseOrExpression(ctx.orExpression());
+    } else if (ctx.LIKE() != null) {
+      if (ctx.suffixPath() != null) {
+        path = parseSuffixPath(ctx.suffixPath());
+      } else if (ctx.fullPath() != null) {
+        path = parseFullPath(ctx.fullPath());
+      }
+      if (path == null) {
+        throw new SQLParserException("Path is null, please check the sql.");
+      }
+      return new LikeOperator(FilterType.LIKE, path, ctx.stringLiteral().getText());
     } else {
-      PartialPath path = null;
       if (ctx.TIME() != null || ctx.TIMESTAMP() != null) {
         path = new PartialPath(SQLConstant.getSingleTimeArray());
       }
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBQueryDemoIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBQueryDemoIT.java
index b041b3e..4ab9353 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBQueryDemoIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBQueryDemoIT.java
@@ -545,4 +545,162 @@ public class IoTDBQueryDemoIT {
       Assert.assertNull(e.getMessage());
     }
   }
+
+  @Test
+  public void LikeTest() throws ClassNotFoundException {
+    String[] retArray =
+        new String[] {
+          "1509465600000,v2,true,", "1509465660000,v2,true,", "1509465720000,v1,false,",
+        };
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+
+      statement.setFetchSize(4);
+      Assert.assertEquals(4, statement.getFetchSize());
+      // Matches a string consisting of one lowercase letter and one digit. such as: "v1","v2"
+      boolean hasResultSet =
+          statement.execute(
+              "select hardware,status from root.ln.wf02.wt02 where hardware like '^[a-z][0-9]$' and time < 1509465780000");
+      Assert.assertTrue(hasResultSet);
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time," + "root.ln.wf02.wt02.hardware,root.ln.wf02.wt02.status,",
+                new int[] {
+                  Types.TIMESTAMP, Types.VARCHAR, Types.BOOLEAN,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(3, cnt);
+      }
+
+      retArray =
+          new String[] {
+            "1509465600000,v2,true,",
+            "1509465660000,v2,true,",
+            "1509465720000,v1,false,",
+            "1509465780000,v1,false,",
+            "1509465840000,v1,false,",
+            "1509465900000,v1,false,",
+            "1509465960000,v1,false,",
+            "1509466020000,v1,false,",
+            "1509466080000,v1,false,",
+            "1509466140000,v1,false,",
+          };
+      hasResultSet =
+          statement.execute(
+              "select hardware,status from root.ln.wf02.wt02 where hardware like 'v*' ");
+      Assert.assertTrue(hasResultSet);
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time," + "root.ln.wf02.wt02.hardware,root.ln.wf02.wt02.status,",
+                new int[] {
+                  Types.TIMESTAMP, Types.VARCHAR, Types.BOOLEAN,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(10, cnt);
+      }
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void LikeNonExistTest() throws ClassNotFoundException {
+
+    // Match nonexistent string.'s.' is indicates that the starting with s and the last is any
+    // single character
+    String[] retArray =
+        new String[] {
+          "1509465600000,v2,true,",
+          "1509465660000,v2,true,",
+          "1509465720000,v1,false,",
+          "1509465780000,v1,false,",
+          "1509465840000,v1,false,",
+          "1509465900000,v1,false,",
+          "1509465960000,v1,false,",
+          "1509466020000,v1,false,",
+          "1509466080000,v1,false,",
+          "1509466140000,v1,false,",
+        };
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+
+      boolean hasResultSet =
+          statement.execute(
+              "select hardware,status from root.ln.wf02.wt02 where hardware like 's.' ");
+      Assert.assertTrue(hasResultSet);
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time," + "root.ln.wf02.wt02.hardware,root.ln.wf02.wt02.status,",
+                new int[] {
+                  Types.TIMESTAMP, Types.VARCHAR, Types.BOOLEAN,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(0, cnt);
+      }
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
 }
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java b/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
index e954b47..d158e91 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
@@ -344,4 +344,18 @@ public class LogicalPlanSmallTest {
     }
     Assert.assertEquals("Invalid delete range: [6, 0]", errorMsg);
   }
+
+  @Test
+  public void testLikeQuery() {
+    String sqlStr = "SELECT a FROM root.sg.* WHERE a LIKE 'string'";
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+    Assert.assertEquals(QueryOperator.class, op.getClass());
+    QueryOperator queryOperator = (QueryOperator) op;
+    Assert.assertEquals(Operator.OperatorType.QUERY, queryOperator.getType());
+    Assert.assertEquals(
+        "a",
+        queryOperator.getSelectComponent().getResultColumns().get(0).getExpression().toString());
+    Assert.assertEquals(
+        "root.sg.*", queryOperator.getFromComponent().getPrefixPaths().get(0).getFullPath());
+  }
 }
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java b/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
index fe7f783..404e632 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/physical/PhysicalPlanTest.java
@@ -1480,4 +1480,21 @@ public class PhysicalPlanTest {
     SetStorageGroupPlan plan = (SetStorageGroupPlan) processor.parseSQLToPhysicalPlan(sqlStr);
     assertEquals("SetStorageGroup{root.sg}", plan.toString());
   }
+
+  @Test
+  public void testLikeQuery() throws QueryProcessException, MetadataException {
+    IoTDB.metaManager.createTimeseries(
+        new PartialPath("root.vehicle.d5.s1"),
+        TSDataType.TEXT,
+        TSEncoding.PLAIN,
+        CompressionType.UNCOMPRESSED,
+        null);
+
+    String sqlStr = "SELECT * FROM root.vehicle.d5 WHERE s1 LIKE 'string*'";
+    PhysicalPlan plan = processor.parseSQLToPhysicalPlan(sqlStr);
+    IExpression queryFilter = ((RawDataQueryPlan) plan).getExpression();
+    IExpression expect =
+        new SingleSeriesExpression(new Path("root.vehicle.d5", "s1"), ValueFilter.like("string*"));
+    assertEquals(expect.toString(), queryFilter.toString());
+  }
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/ValueFilter.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/ValueFilter.java
index 036cfed..8a8e547 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/ValueFilter.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/ValueFilter.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.tsfile.read.filter.operator.Eq;
 import org.apache.iotdb.tsfile.read.filter.operator.Gt;
 import org.apache.iotdb.tsfile.read.filter.operator.GtEq;
 import org.apache.iotdb.tsfile.read.filter.operator.In;
+import org.apache.iotdb.tsfile.read.filter.operator.Like;
 import org.apache.iotdb.tsfile.read.filter.operator.Lt;
 import org.apache.iotdb.tsfile.read.filter.operator.LtEq;
 import org.apache.iotdb.tsfile.read.filter.operator.NotEq;
@@ -68,6 +69,10 @@ public class ValueFilter {
     return new ValueNotEq(value);
   }
 
+  public static <T extends Comparable<T>> ValueLike<T> like(String value) {
+    return new ValueLike(value);
+  }
+
   public static class ValueIn<T extends Comparable<T>> extends In<T> {
 
     private ValueIn(Set<T> values, boolean not) {
@@ -233,4 +238,26 @@ public class ValueFilter {
       return !this.value.equals(v);
     }
   }
+
+  public static class ValueLike<T extends Comparable<T>> extends Like<T> {
+
+    private ValueLike(String value) {
+      super(value, FilterType.VALUE_FILTER);
+    }
+  }
+
+  public static class VectorValueLike<T extends Comparable<T>> extends ValueLike<T> {
+
+    private final int index;
+
+    private VectorValueLike(String value, int index) {
+      super(value);
+      this.index = index;
+    }
+
+    public boolean satisfy(long time, TsPrimitiveType[] values) {
+      Object v = filterType == FilterType.TIME_FILTER ? time : values[index].getValue();
+      return this.value.equals(v);
+    }
+  }
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
index 2381924..46d9666 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
@@ -30,5 +30,6 @@ public enum FilterSerializeId {
   NEQ,
   NOT,
   OR,
-  IN
+  IN,
+  LIKE
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Like.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Like.java
new file mode 100644
index 0000000..572a0f3
--- /dev/null
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Like.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.tsfile.read.filter.operator;
+
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+import org.apache.iotdb.tsfile.read.filter.factory.FilterSerializeId;
+import org.apache.iotdb.tsfile.read.filter.factory.FilterType;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Like.
+ *
+ * @param <T> comparable data type
+ */
+public class Like<T extends Comparable<T>> implements Filter {
+
+  private static final long serialVersionUID = 2171102599229260789L;
+
+  protected String value;
+
+  protected FilterType filterType;
+
+  protected Pattern pattern;
+
+  private Like() {}
+
+  public Like(String value, FilterType filterType) {
+    this.value = value;
+    this.filterType = filterType;
+    try {
+      this.pattern = Pattern.compile(this.value);
+    } catch (PatternSyntaxException e) {
+      throw new PatternSyntaxException("Regular expression error", value.toString(), e.getIndex());
+    }
+  }
+
+  @Override
+  public boolean satisfy(Statistics statistics) {
+    return true;
+  }
+
+  @Override
+  public boolean satisfy(long time, Object value) {
+    if (filterType != FilterType.VALUE_FILTER) {
+      return false;
+    }
+    return pattern.matcher(value.toString()).find();
+  }
+
+  @Override
+  public boolean satisfyStartEndTime(long startTime, long endTime) {
+    return true;
+  }
+
+  @Override
+  public boolean containStartEndTime(long startTime, long endTime) {
+    return true;
+  }
+
+  @Override
+  public Filter copy() {
+    return new Like(value, filterType);
+  }
+
+  @Override
+  public void serialize(DataOutputStream outputStream) {
+    try {
+      outputStream.write(getSerializeId().ordinal());
+      outputStream.write(filterType.ordinal());
+      ReadWriteIOUtils.writeObject(value, outputStream);
+    } catch (IOException ex) {
+      throw new IllegalArgumentException("Failed to serialize outputStream of type:", ex);
+    }
+  }
+
+  @Override
+  public void deserialize(ByteBuffer buffer) {
+    filterType = FilterType.values()[buffer.get()];
+    value = ReadWriteIOUtils.readString(buffer);
+  }
+
+  @Override
+  public String toString() {
+    return filterType + " is " + value;
+  }
+
+  @Override
+  public FilterSerializeId getSerializeId() {
+    return FilterSerializeId.LIKE;
+  }
+}