This is an automated email from the ASF dual-hosted git repository.
haonan 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 3bacba0 [IOTDB-1586] Support mysql-style Like clause (#3738)
3bacba0 is described below
commit 3bacba053cd363eb17ed6c94ee3cffe61adee385
Author: Superainbower <[email protected]>
AuthorDate: Thu Aug 26 13:11:21 2021 +0800
[IOTDB-1586] Support mysql-style Like clause (#3738)
Co-authored-by: Jialin Qiao <[email protected]>
---
.../antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4 | 6 +-
docs/UserGuide/Appendix/SQL-Reference.md | 39 ++++-
docs/zh/UserGuide/Appendix/SQL-Reference.md | 39 ++++-
.../iotdb/db/qp/constant/FilterConstant.java | 3 +
.../iotdb/db/qp/logical/crud/LikeOperator.java | 5 -
.../{LikeOperator.java => RegexpOperator.java} | 32 ++--
.../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java | 7 +-
.../apache/iotdb/db/integration/IoTDBLikeIT.java | 169 +++++++++++++++++++++
.../iotdb/db/integration/IoTDBQueryDemoIT.java | 10 +-
.../iotdb/db/qp/logical/LogicalPlanSmallTest.java | 4 +-
.../iotdb/db/qp/physical/PhysicalPlanTest.java | 4 +-
.../iotdb/tsfile/read/filter/ValueFilter.java | 27 ++++
.../read/filter/factory/FilterSerializeId.java | 1 +
.../iotdb/tsfile/read/filter/operator/Like.java | 50 +++++-
.../filter/operator/{Like.java => Regexp.java} | 14 +-
15 files changed, 349 insertions(+), 61 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 c6dc1f9..3f6fb5f 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
@@ -213,7 +213,7 @@ predicate
: (TIME | TIMESTAMP | suffixPath | fullPath) comparisonOperator constant
| (TIME | TIMESTAMP | suffixPath | fullPath) inClause
| OPERATOR_NOT? LR_BRACKET orExpression RR_BRACKET
- | (suffixPath | fullPath) LIKE stringLiteral
+ | (suffixPath | fullPath) (REGEXP | LIKE) stringLiteral
;
inClause
@@ -1286,6 +1286,10 @@ LIKE
: L I K E
;
+REGEXP
+ : R E G E X P
+ ;
+
TOLERANCE
: T O L E R A N C E
;
diff --git a/docs/UserGuide/Appendix/SQL-Reference.md
b/docs/UserGuide/Appendix/SQL-Reference.md
index 3b3dea1..69f46ec 100644
--- a/docs/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/UserGuide/Appendix/SQL-Reference.md
@@ -716,22 +716,45 @@ In this situation, it will throws an exception if *
corresponds to multiple sens
```
-* Like Statement
+* Regexp Statement
-Fuzzy query only supports regular expressions with data type of text and Java
standard library style when matching
+Regexp Statement only supports regular expressions with Java standard library
style on timeseries which is TEXT data type
```
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
+predicate : (suffixPath | fullPath) REGEXP regularExpression
+regularExpression: Java standard regularexpression, like '^[a-z][0-9]$',
[details](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html)
+
+Eg. select s1 from root.sg.d1 where s1 regexp '^[0-9]*$'
+Eg. select s1, s2 FROM root.sg.d1 where s1 regexp
'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$' and s2 regexp '^\d{15}|\d{18}$'
+Eg. select * from root.sg.d1 where s1 regexp '^[a-zA-Z]\w{5,17}$'
+Eg. select * from root.sg.d1 where s1 regexp '^\d{4}-\d{1,2}-\d{1,2}' and time
> 100
+```
-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
+* Like Statement
+
+The usage of LIKE Statement similar with mysql, but only support timeseries
which is TEXT data type
+```
+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 likeExpression
+likeExpression : string that may contains "%" or "_", while "%value" means a
string that ends with the value, "value%" means a string starts with the
value, "%value%" means string that contains values, and "_" represents any
character.
+
+Eg. select s1 from root.sg.d1 where s1 like 'abc'
+Eg. select s1, s2 from root.sg.d1 where s1 like 'a%bc'
+Eg. select * from root.sg.d1 where s1 like 'abc_'
+Eg. select * from root.sg.d1 where s1 like 'abc\%' and time > 100
+In this situation, '\%' means '%' will be escaped
+The result set will be like:
+| Time | Path | Value |
+| --- | ------------ | ----- |
+| 200 | root.sg.d1.s1| abc% |
```
## Database Management Statement
diff --git a/docs/zh/UserGuide/Appendix/SQL-Reference.md
b/docs/zh/UserGuide/Appendix/SQL-Reference.md
index 64ff231..eb8f882 100644
--- a/docs/zh/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/zh/UserGuide/Appendix/SQL-Reference.md
@@ -705,22 +705,45 @@ E.g. select * as temperature from root.sg.d1
这种情况如果 * 匹配多个传感器,则无法正常显示。
```
-* Like 语句
+* Regexp 语句
-模糊查询,仅支持数据类型为 TEXT,匹配时为 Java 标准库风格的正则表达式
+Regexp语句仅支持数据类型为 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
+predicate : (suffixPath | fullPath) REGEXP regularExpression
+regularExpression: Java standard regularexpression, like '^[a-z][0-9]$',
[details](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html)
+
+Eg. select s1 from root.sg.d1 where s1 regexp '^[0-9]*$'
+Eg. select s1, s2 FROM root.sg.d1 where s1 regexp
'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$' and s2 regexp '^\d{15}|\d{18}$'
+Eg. select * from root.sg.d1 where s1 regexp '^[a-zA-Z]\w{5,17}$'
+Eg. select * from root.sg.d1 where s1 regexp '^\d{4}-\d{1,2}-\d{1,2}' and time
> 100
+```
-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
+* Like 语句
+
+Like语句的用法和mysql相同, 但是仅支持对数据类型为 TEXT的列进行过滤
+```
+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 likeExpression
+likeExpression : string that may contains "%" or "_", while "%value" means a
string that ends with the value, "value%" means a string starts with the
value, "%value%" means string that contains values, and "_" represents any
character.
+
+Eg. select s1 from root.sg.d1 where s1 like 'abc'
+Eg. select s1, s2 from root.sg.d1 where s1 like 'abc%'
+Eg. select * from root.sg.d1 where s1 like 'abc_'
+Eg. select * from root.sg.d1 where s1 like 'abc\%'
+这种情况,'\%'表示'%'将会被转义
+结果集将显示为:
+| Time | Path | Value |
+| --- | ------------ | ----- |
+| 200 | root.sg.d1.s1| abc% |
```
## 数据库管理语句
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 d90dc16..9c43771 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
@@ -45,6 +45,7 @@ public class FilterConstant {
GREATERTHANOREQUALTO,
GREATERTHAN,
IN,
+ REGEXP,
LIKE
}
@@ -56,6 +57,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.REGEXP, FilterType.REGEXP);
lexerToFilterType.put(SqlBaseLexer.LIKE, FilterType.LIKE);
}
@@ -83,6 +85,7 @@ public class FilterConstant {
filterNames.put(FilterType.GREATERTHANOREQUALTO, "greaterthan_or_equalto");
filterNames.put(FilterType.GREATERTHAN, "greaterthan");
filterNames.put(FilterType.IN, "in");
+ filterNames.put(FilterType.REGEXP, "regexp");
filterNames.put(FilterType.LIKE, "like");
}
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
index 10c9c6e..d552b9c 100644
--- 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
@@ -30,9 +30,6 @@ 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;
@@ -41,8 +38,6 @@ 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) {
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/RegexpOperator.java
similarity index 80%
copy from
server/src/main/java/org/apache/iotdb/db/qp/logical/crud/LikeOperator.java
copy to
server/src/main/java/org/apache/iotdb/db/qp/logical/crud/RegexpOperator.java
index 10c9c6e..5988d63 100644
--- 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/RegexpOperator.java
@@ -30,22 +30,16 @@ 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);
+public class RegexpOperator extends FunctionOperator {
protected String value;
- public LikeOperator(FilterType filterType, PartialPath path, String value) {
+ public RegexpOperator(FilterType filterType, PartialPath path, String value)
{
super(filterType);
this.singlePath = path;
this.value = value;
@@ -64,12 +58,12 @@ public class LikeOperator extends FunctionOperator {
}
IUnaryExpression ret;
if (type != TEXT) {
- throw new LogicalOperatorException(type.toString(), "Only TEXT is
supported in 'Like'");
+ throw new LogicalOperatorException(type.toString(), "Only TEXT is
supported in 'Regexp'");
} else if (value.startsWith("\"") && value.endsWith("\"")) {
throw new LogicalOperatorException(value, "Please use single quotation
marks");
} else {
ret =
- Like.getUnaryExpression(
+ RegexpOperator.Regexp.getUnaryExpression(
singlePath,
(value.startsWith("'") && value.endsWith("'"))
? value.substring(1, value.length() - 1)
@@ -78,14 +72,14 @@ public class LikeOperator extends FunctionOperator {
return new Pair<>(ret, singlePath.getFullPath());
}
- private static class Like {
+ private static class Regexp {
public static <T extends Comparable<T>> IUnaryExpression
getUnaryExpression(
PartialPath path, String value) {
- return new SingleSeriesExpression(path, ValueFilter.like(value));
+ return new SingleSeriesExpression(path, ValueFilter.regexp(value));
}
public <T extends Comparable<T>> Filter getValueFilter(String value) {
- return ValueFilter.like(value);
+ return ValueFilter.regexp(value);
}
}
@@ -95,14 +89,14 @@ public class LikeOperator extends FunctionOperator {
for (int i = 0; i < spaceNum; i++) {
sc.addTail(" ");
}
- sc.addTail(singlePath.getFullPath(), getFilterSymbol(), value, ",
single\n");
+ sc.addTail(singlePath.getFullPath(), value, ", single\n");
return sc.toString();
}
@Override
- public LikeOperator copy() {
- LikeOperator ret =
- new LikeOperator(this.filterType, new
PartialPath(singlePath.getNodes().clone()), value);
+ public RegexpOperator copy() {
+ RegexpOperator ret =
+ new RegexpOperator(this.filterType, new
PartialPath(singlePath.getNodes().clone()), value);
ret.isLeaf = isLeaf;
ret.isSingle = isSingle;
ret.pathSet = pathSet;
@@ -114,7 +108,7 @@ public class LikeOperator extends FunctionOperator {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
- LikeOperator that = (LikeOperator) o;
+ RegexpOperator that = (RegexpOperator) o;
return Objects.equals(value, that.value);
}
@@ -125,6 +119,6 @@ public class LikeOperator extends FunctionOperator {
@Override
public String toString() {
- return "[" + singlePath.getFullPath() + getFilterSymbol() + value + "]";
+ return "[" + singlePath.getFullPath() + 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 330bcad..50052f5 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
@@ -46,6 +46,7 @@ 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.RegexpOperator;
import org.apache.iotdb.db.qp.logical.crud.SelectComponent;
import org.apache.iotdb.db.qp.logical.crud.SelectIntoOperator;
import org.apache.iotdb.db.qp.logical.crud.SpecialClauseComponent;
@@ -2068,7 +2069,7 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
return notOp;
} else if (ctx.LR_BRACKET() != null && ctx.OPERATOR_NOT() == null) {
return parseOrExpression(ctx.orExpression());
- } else if (ctx.LIKE() != null) {
+ } else if (ctx.REGEXP() != null || ctx.LIKE() != null) {
if (ctx.suffixPath() != null) {
path = parseSuffixPath(ctx.suffixPath());
} else if (ctx.fullPath() != null) {
@@ -2077,7 +2078,9 @@ public class IoTDBSqlVisitor extends
SqlBaseBaseVisitor<Operator> {
if (path == null) {
throw new SQLParserException("Path is null, please check the sql.");
}
- return new LikeOperator(FilterType.LIKE, path,
ctx.stringLiteral().getText());
+ return ctx.REGEXP() != null
+ ? new RegexpOperator(FilterType.REGEXP, path,
ctx.stringLiteral().getText())
+ : new LikeOperator(FilterType.LIKE, path,
ctx.stringLiteral().getText());
} else {
if (ctx.TIME() != null || ctx.TIMESTAMP() != null) {
path = new PartialPath(SQLConstant.getSingleTimeArray());
diff --git
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBLikeIT.java
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBLikeIT.java
new file mode 100644
index 0000000..030397e
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBLikeIT.java
@@ -0,0 +1,169 @@
+/*
+ * 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.integration;
+
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class IoTDBLikeIT {
+ private static List<String> sqls = new ArrayList<>();
+ private static Connection connection;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvironmentUtils.closeStatMonitor();
+ initCreateSQLStatement();
+ EnvironmentUtils.envSetUp();
+ insertData();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ close();
+ EnvironmentUtils.cleanEnv();
+ }
+
+ private static void close() {
+ if (Objects.nonNull(connection)) {
+ try {
+ connection.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static void initCreateSQLStatement() {
+ sqls.add("SET STORAGE GROUP TO root.t1");
+ sqls.add("CREATE TIMESERIES root.t1.wf01.wt01.status WITH DATATYPE=TEXT,
ENCODING=PLAIN");
+ sqls.add("CREATE TIMESERIES root.t1.wf01.wt01.temperature WITH
DATATYPE=FLOAT, ENCODING=RLE");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465600000,'1',12.1)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465660000,'14',13.1)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465720000,'616',5.5)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465780000,'626',8.1)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465840000,'6116',4.3)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465900000,'6%16',10.3)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509465960000,'8[sS]*',11.3)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509466020000,'%123',18.3)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509466080000,'123%',18.3)");
+ sqls.add(
+ "insert into root.t1.wf01.wt01 (time,status,temperature) values
(1509466090000,'\\',10.3)");
+ }
+
+ private static void insertData() throws ClassNotFoundException, SQLException
{
+ Class.forName(Config.JDBC_DRIVER_NAME);
+ connection =
+ DriverManager.getConnection(Config.IOTDB_URL_PREFIX +
"127.0.0.1:6667/", "root", "root");
+ Statement statement = connection.createStatement();
+
+ for (String sql : sqls) {
+ statement.execute(sql);
+ }
+ statement.close();
+ }
+
+ @Test
+ public void testLike() throws SQLException {
+ Statement st0 = connection.createStatement();
+ boolean hasResultSet =
+ st0.execute("select status from root.t1.wf01.wt01 where status like
'1'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("1", outputResultStr(st0.getResultSet()));
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals(
+ "1,14,616,626,6116,6%16,8[sS]*,%123,123%,\\",
outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '1%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("1,14,123%", outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '%1%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("1,14,616,6116,6%16,%123,123%",
outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '6%6'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("616,626,6116,6%16",
outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '1_'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("14", outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '6_1%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("6116,6%16", outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '6\\%%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("6%16", outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '\\%%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("%123", outputResultStr(st0.getResultSet()));
+
+ hasResultSet = st0.execute("select status from root.t1.wf01.wt01 where
status like '%\\%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("123%", outputResultStr(st0.getResultSet()));
+
+ hasResultSet =
+ st0.execute("select status from root.t1.wf01.wt01 where status like
'%\\\\\\\\%'");
+ Assert.assertTrue(hasResultSet);
+ Assert.assertEquals("\\", outputResultStr(st0.getResultSet()));
+ }
+
+ @Test(expected = Exception.class)
+ public void testLikeNonTextCloumn() throws SQLException {
+ Statement st1 = connection.createStatement();
+ st1.execute("select * from root.t1.wf01.wt01 where temperature like '1'");
+ }
+
+ private String outputResultStr(ResultSet resultSet) throws SQLException {
+ StringBuilder resultBuilder = new StringBuilder();
+ while (resultSet.next()) {
+ resultBuilder.append(resultSet.getString(2)).append(",");
+ }
+ String result = resultBuilder.toString();
+ return result.substring(0, result.length() - 1);
+ }
+}
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 4ab9353..217028f 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
@@ -547,7 +547,7 @@ public class IoTDBQueryDemoIT {
}
@Test
- public void LikeTest() throws ClassNotFoundException {
+ public void RegexpTest() throws ClassNotFoundException {
String[] retArray =
new String[] {
"1509465600000,v2,true,", "1509465660000,v2,true,",
"1509465720000,v1,false,",
@@ -564,7 +564,7 @@ public class IoTDBQueryDemoIT {
// 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");
+ "select hardware,status from root.ln.wf02.wt02 where hardware
regexp '^[a-z][0-9]$' and time < 1509465780000");
Assert.assertTrue(hasResultSet);
try (ResultSet resultSet = statement.getResultSet()) {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
@@ -608,7 +608,7 @@ public class IoTDBQueryDemoIT {
};
hasResultSet =
statement.execute(
- "select hardware,status from root.ln.wf02.wt02 where hardware
like 'v*' ");
+ "select hardware,status from root.ln.wf02.wt02 where hardware
regexp 'v*' ");
Assert.assertTrue(hasResultSet);
try (ResultSet resultSet = statement.getResultSet()) {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
@@ -644,7 +644,7 @@ public class IoTDBQueryDemoIT {
}
@Test
- public void LikeNonExistTest() throws ClassNotFoundException {
+ public void RegexpNonExistTest() throws ClassNotFoundException {
// Match nonexistent string.'s.' is indicates that the starting with s and
the last is any
// single character
@@ -669,7 +669,7 @@ public class IoTDBQueryDemoIT {
boolean hasResultSet =
statement.execute(
- "select hardware,status from root.ln.wf02.wt02 where hardware
like 's.' ");
+ "select hardware,status from root.ln.wf02.wt02 where hardware
regexp 's.' ");
Assert.assertTrue(hasResultSet);
try (ResultSet resultSet = statement.getResultSet()) {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
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 d158e91..b07461a 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
@@ -346,8 +346,8 @@ public class LogicalPlanSmallTest {
}
@Test
- public void testLikeQuery() {
- String sqlStr = "SELECT a FROM root.sg.* WHERE a LIKE 'string'";
+ public void testRegexpQuery() {
+ String sqlStr = "SELECT a FROM root.sg.* WHERE a REGEXP 'string'";
Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
Assert.assertEquals(QueryOperator.class, op.getClass());
QueryOperator queryOperator = (QueryOperator) op;
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 404e632..36df8d3 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
@@ -1482,7 +1482,7 @@ public class PhysicalPlanTest {
}
@Test
- public void testLikeQuery() throws QueryProcessException, MetadataException {
+ public void testRegexpQuery() throws QueryProcessException,
MetadataException {
IoTDB.metaManager.createTimeseries(
new PartialPath("root.vehicle.d5.s1"),
TSDataType.TEXT,
@@ -1490,7 +1490,7 @@ public class PhysicalPlanTest {
CompressionType.UNCOMPRESSED,
null);
- String sqlStr = "SELECT * FROM root.vehicle.d5 WHERE s1 LIKE 'string*'";
+ String sqlStr = "SELECT * FROM root.vehicle.d5 WHERE s1 REGEXP 'string*'";
PhysicalPlan plan = processor.parseSQLToPhysicalPlan(sqlStr);
IExpression queryFilter = ((RawDataQueryPlan) plan).getExpression();
IExpression expect =
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 8a8e547..3dcfbe9 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
@@ -29,6 +29,7 @@ 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;
import org.apache.iotdb.tsfile.read.filter.operator.NotFilter;
+import org.apache.iotdb.tsfile.read.filter.operator.Regexp;
import org.apache.iotdb.tsfile.utils.TsPrimitiveType;
import java.util.Set;
@@ -69,6 +70,10 @@ public class ValueFilter {
return new ValueNotEq(value);
}
+ public static <T extends Comparable<T>> ValueRegexp<T> regexp(String value) {
+ return new ValueRegexp(value);
+ }
+
public static <T extends Comparable<T>> ValueLike<T> like(String value) {
return new ValueLike(value);
}
@@ -239,6 +244,28 @@ public class ValueFilter {
}
}
+ public static class ValueRegexp<T extends Comparable<T>> extends Regexp<T> {
+
+ private ValueRegexp(String value) {
+ super(value, FilterType.VALUE_FILTER);
+ }
+ }
+
+ public static class VectorValueRegexp<T extends Comparable<T>> extends
ValueRegexp<T> {
+
+ private final int index;
+
+ private VectorValueRegexp(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);
+ }
+ }
+
public static class ValueLike<T extends Comparable<T>> extends Like<T> {
private ValueLike(String value) {
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 46d9666..ec7f348 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
@@ -31,5 +31,6 @@ public enum FilterSerializeId {
NOT,
OR,
IN,
+ REGEXP,
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
index 572a0f3..7f32f7b 100644
---
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
@@ -47,11 +47,36 @@ public class Like<T extends Comparable<T>> implements
Filter {
private Like() {}
+ /**
+ * The main idea of this part comes from
+ *
https://codereview.stackexchange.com/questions/36861/convert-sql-like-to-regex/36864
+ */
public Like(String value, FilterType filterType) {
this.value = value;
this.filterType = filterType;
try {
- this.pattern = Pattern.compile(this.value);
+ String unescapeValue = unescapeString(value);
+ String specialRegexStr = ".^$*+?{}[]|()";
+ StringBuilder patternStrBuild = new StringBuilder();
+ patternStrBuild.append("^");
+ for (int i = 0; i < unescapeValue.length(); i++) {
+ String ch = String.valueOf(unescapeValue.charAt(i));
+ if (specialRegexStr.contains(ch)) ch = "\\" + unescapeValue.charAt(i);
+ if ((i == 0)
+ || (i > 0 && !"\\".equals(String.valueOf(unescapeValue.charAt(i -
1))))
+ || (i >= 2
+ && "\\\\"
+ .equals(
+ patternStrBuild.substring(
+ patternStrBuild.length() - 2,
patternStrBuild.length())))) {
+ String replaceStr = ch.replace("%", ".*?").replace("_", ".");
+ patternStrBuild.append(replaceStr);
+ } else {
+ patternStrBuild.append(ch);
+ }
+ }
+ patternStrBuild.append("$");
+ this.pattern = Pattern.compile(patternStrBuild.toString());
} catch (PatternSyntaxException e) {
throw new PatternSyntaxException("Regular expression error",
value.toString(), e.getIndex());
}
@@ -111,4 +136,27 @@ public class Like<T extends Comparable<T>> implements
Filter {
public FilterSerializeId getSerializeId() {
return FilterSerializeId.LIKE;
}
+
+ /**
+ * This Method is for unescaping strings except '\' before special string
'%', '_', '\', because
+ * we need to use '\' to judege whether to replace this to regexp string
+ */
+ public String unescapeString(String value) {
+ String out = "";
+ for (int i = 0; i < value.length(); i++) {
+ String ch = String.valueOf(value.charAt(i));
+ if (ch.equals("\\")) {
+ if (i < value.length() - 1) {
+ String nextChar = String.valueOf(value.charAt(i + 1));
+ if (nextChar.equals("%") || nextChar.equals("_") ||
nextChar.equals("\\")) {
+ out = out + ch;
+ }
+ if (nextChar.equals("\\")) i++;
+ }
+ } else {
+ out = out + ch;
+ }
+ }
+ return out;
+ }
}
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/Regexp.java
similarity index 91%
copy from
tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Like.java
copy to
tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Regexp.java
index 572a0f3..5aa95bb 100644
---
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/Regexp.java
@@ -31,13 +31,11 @@ import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
- * Like.
+ * Regexp.
*
* @param <T> comparable data type
*/
-public class Like<T extends Comparable<T>> implements Filter {
-
- private static final long serialVersionUID = 2171102599229260789L;
+public class Regexp<T extends Comparable<T>> implements Filter {
protected String value;
@@ -45,9 +43,9 @@ public class Like<T extends Comparable<T>> implements Filter {
protected Pattern pattern;
- private Like() {}
+ private Regexp() {}
- public Like(String value, FilterType filterType) {
+ public Regexp(String value, FilterType filterType) {
this.value = value;
this.filterType = filterType;
try {
@@ -82,7 +80,7 @@ public class Like<T extends Comparable<T>> implements Filter {
@Override
public Filter copy() {
- return new Like(value, filterType);
+ return new Regexp(value, filterType);
}
@Override
@@ -109,6 +107,6 @@ public class Like<T extends Comparable<T>> implements
Filter {
@Override
public FilterSerializeId getSerializeId() {
- return FilterSerializeId.LIKE;
+ return FilterSerializeId.REGEXP;
}
}