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;
+ }
+}