[IOTDB-3695] Implememtation of having clause (#6840)
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
index 9485865..12f4941 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
@@ -84,6 +84,7 @@
| GLOBAL
| GRANT
| GROUP
+ | HAVING
| INDEX
| INFO
| INSERT
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index 127e497..3cf6157 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -384,9 +384,9 @@
specialClause
: specialLimit #specialLimitStatement
| orderByClause specialLimit? #orderByTimeStatement
- | groupByTimeClause orderByClause? specialLimit? #groupByTimeStatement
- | groupByFillClause orderByClause? specialLimit? #groupByFillStatement
- | groupByLevelClause orderByClause? specialLimit? #groupByLevelStatement
+ | groupByTimeClause havingClause? orderByClause? specialLimit? #groupByTimeStatement
+ | groupByFillClause havingClause? orderByClause? specialLimit? #groupByFillStatement
+ | groupByLevelClause havingClause? orderByClause? specialLimit? #groupByLevelStatement
| fillClause orderByClause? specialLimit? #fillStatement
;
@@ -397,6 +397,10 @@
| alignByDeviceClauseOrDisableAlign #alignByDeviceClauseOrDisableAlignStatement
;
+havingClause
+ : HAVING expression
+ ;
+
alignByDeviceClauseOrDisableAlign
: alignByDeviceClause
| disableAlign
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
index a87ede3..5ad902f 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
@@ -251,6 +251,10 @@
: G R O U P
;
+HAVING
+ : H A V I N G
+ ;
+
INDEX
: I N D E X
;
diff --git a/docs/UserGuide/Query-Data/Aggregate-Query.md b/docs/UserGuide/Query-Data/Aggregate-Query.md
index a55a145..3cc502b 100644
--- a/docs/UserGuide/Query-Data/Aggregate-Query.md
+++ b/docs/UserGuide/Query-Data/Aggregate-Query.md
@@ -452,4 +452,98 @@
+-----------------------------+-------------------------+
Total line number = 7
It costs 0.004s
+```
+
+## Aggregate result filtering
+
+If you want to filter the results of aggregate queries,
+you can use the `HAVING` clause after the `GROUP BY` clause.
+
+> NOTE:
+>
+> 1.The expression in HAVING clause must consist of aggregate values; the original sequence cannot appear alone.
+> The following usages are incorrect:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms) having sum(s1) > s1
+> select count(s1) from root.** group by ([1,3),1ms) having s1 > 1
+> ```
+> 2.When filtering the `GROUP BY LEVEL` result, the PATH in `SELECT` and `HAVING` can only have one node.
+> The following usages are incorrect:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1
+> select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1
+> ```
+
+Here are a few examples of using the 'HAVING' clause to filter aggregate results.
+
+Aggregation result 1:
+
+```
++-----------------------------+---------------------+---------------------+
+| Time|count(root.test.*.s1)|count(root.test.*.s2)|
++-----------------------------+---------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00| 4| 4|
+|1970-01-01T08:00:00.003+08:00| 1| 0|
+|1970-01-01T08:00:00.005+08:00| 2| 4|
+|1970-01-01T08:00:00.007+08:00| 3| 2|
+|1970-01-01T08:00:00.009+08:00| 4| 4|
++-----------------------------+---------------------+---------------------+
+```
+
+Aggregation result filtering query 1:
+
+```sql
+ select count(s1) from root.** group by ([1,11),2ms), level=1 having count(s2) > 1
+```
+
+Filtering result 1:
+
+```
++-----------------------------+---------------------+
+| Time|count(root.test.*.s1)|
++-----------------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00| 4|
+|1970-01-01T08:00:00.005+08:00| 2|
+|1970-01-01T08:00:00.009+08:00| 4|
++-----------------------------+---------------------+
+```
+
+Aggregation result 2:
+
+```
++-----------------------------+-------------+---------+---------+
+| Time| Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg1| 1| 0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg1| 2| 1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1| 2| 2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2| 2| 2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg2| 0| 0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2| 1| 2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg2| 1| 1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2| 2| 2|
++-----------------------------+-------------+---------+---------+
+```
+
+Aggregation result filtering query 2:
+
+```sql
+ select count(s1), count(s2) from root.** group by ([1,11),2ms) having count(s2) > 1 align by device
+```
+
+Filtering result 2:
+
+```
++-----------------------------+-------------+---------+---------+
+| Time| Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1| 2| 2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2| 2| 2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2| 1| 2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2| 2| 2|
++-----------------------------+-------------+---------+---------+
```
\ No newline at end of file
diff --git a/docs/zh/UserGuide/Query-Data/Aggregate-Query.md b/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
index 51950fd..12e34e0 100644
--- a/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
+++ b/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
@@ -446,4 +446,97 @@
+-----------------------------+-------------------------+
Total line number = 7
It costs 0.004s
+```
+
+## 聚合结果过滤
+
+如果想对聚合查询的结果进行过滤,可以在`GROUP BY`子句之后使用`HAVING`子句
+
+> 注意:
+>
+> 1.`HAVING`子句中的过滤条件必须由聚合值构成,原始序列不能单独出现。
+> 下列使用方式是不正确的:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms) having sum(s1) > s1
+> select count(s1) from root.** group by ([1,3),1ms) having s1 > 1
+> ```
+> 2.对`GROUP BY LEVEL`结果进行过滤时,`SELECT`和`HAVING`中出现的PATH只能有一级。
+> 下列使用方式是不正确的:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1
+> select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1
+> ```
+
+以下为几个使用`HAVING`子句对聚合结果进行过滤对例子
+
+聚合结果1:
+
+```
++-----------------------------+---------------------+---------------------+
+| Time|count(root.test.*.s1)|count(root.test.*.s2)|
++-----------------------------+---------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00| 4| 4|
+|1970-01-01T08:00:00.003+08:00| 1| 0|
+|1970-01-01T08:00:00.005+08:00| 2| 4|
+|1970-01-01T08:00:00.007+08:00| 3| 2|
+|1970-01-01T08:00:00.009+08:00| 4| 4|
++-----------------------------+---------------------+---------------------+
+```
+
+聚合结果过滤查询1:
+
+```sql
+ select count(s1) from root.** group by ([1,11),2ms), level=1 having count(s2) > 2
+```
+
+过滤结果1:
+
+```
++-----------------------------+---------------------+
+| Time|count(root.test.*.s1)|
++-----------------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00| 4|
+|1970-01-01T08:00:00.005+08:00| 2|
+|1970-01-01T08:00:00.009+08:00| 4|
++-----------------------------+---------------------+
+```
+
+聚合结果2:
+
+```
++-----------------------------+-------------+---------+---------+
+| Time| Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg1| 1| 0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg1| 2| 1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1| 2| 2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2| 2| 2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg2| 0| 0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2| 1| 2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg2| 1| 1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2| 2| 2|
++-----------------------------+-------------+---------+---------+
+```
+
+聚合结果过滤查询2:
+
+```sql
+ select count(s1), count(s2) from root.** group by ([1,11),2ms) having count(s2) > 1 align by device
+```
+
+过滤结果2:
+
+```
++-----------------------------+-------------+---------+---------+
+| Time| Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1| 1| 2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1| 2| 2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2| 2| 2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2| 1| 2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2| 2| 2|
++-----------------------------+-------------+---------+---------+
```
\ No newline at end of file
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java
new file mode 100644
index 0000000..aff5e44
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java
@@ -0,0 +1,247 @@
+/*
+ * 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.it.groupby;
+
+import org.apache.iotdb.it.env.ConfigFactory;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.assertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareData;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualWithDescOrderTest;
+import static org.apache.iotdb.itbase.constant.TestConstant.TIMESTAMP_STR;
+import static org.apache.iotdb.itbase.constant.TestConstant.count;
+import static org.apache.iotdb.itbase.constant.TestConstant.sum;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({ClusterIT.class}) // TODO After old StandAlone remove
+public class IoTDBHavingIT {
+ private static final String[] SQLs =
+ new String[] {
+ "SET STORAGE GROUP TO root.test",
+ "CREATE TIMESERIES root.test.sg1.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg1.s2 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg1.s3 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg1.s4 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg2.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg2.s2 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg2.s3 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg2.s4 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "CREATE ALIGNED TIMESERIES root.test.sg3(s5 INT32, s6 BOOLEAN, s7 DOUBLE, s8 INT32)",
+ "CREATE TIMESERIES root.test.sg5.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.test.sg5.s9 WITH DATATYPE=INT32, ENCODING=PLAIN",
+ "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(1, true, 1, 1.0, 1)",
+ "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(1, false, 1, 1.0, 1)",
+ "INSERT INTO root.test.sg1(timestamp, s2) " + "values(2, 2)",
+ "INSERT INTO root.test.sg1(timestamp, s3) " + "values(2, 2.0)",
+ "INSERT INTO root.test.sg1(timestamp, s4) " + "values(2, 2)",
+ "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(2, true, 2, 2.0, 2)",
+ "flush",
+ "INSERT INTO root.test.sg1(timestamp, s1) " + "values(3, false)",
+ "INSERT INTO root.test.sg1(timestamp, s2) " + "values(5, 5)",
+ "INSERT INTO root.test.sg1(timestamp, s3) " + "values(5, 5.0)",
+ "INSERT INTO root.test.sg1(timestamp, s4) " + "values(5, 5)",
+ "INSERT INTO root.test.sg2(timestamp, s2) " + "values(5, 5)",
+ "INSERT INTO root.test.sg2(timestamp, s3) " + "values(5, 5.0)",
+ "INSERT INTO root.test.sg2(timestamp, s4) " + "values(5, 5)",
+ "flush",
+ "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(6, true, 6, 6.0, 6)",
+ "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(6, true, 6, 6.0, 6)",
+ "INSERT INTO root.test.sg1(timestamp, s1) " + "values(7, true)",
+ "INSERT INTO root.test.sg1(timestamp, s3) " + "values(7, 7.0)",
+ "INSERT INTO root.test.sg2(timestamp,s1,s2, s3) " + "values(7, true, 7, 7.0)",
+ "flush",
+ "INSERT INTO root.test.sg1(timestamp, s1) " + "values(8, true)",
+ "INSERT INTO root.test.sg1(timestamp, s2) " + "values(8, 8)",
+ "INSERT INTO root.test.sg1(timestamp, s3) " + "values(8, 8.0)",
+ "INSERT INTO root.test.sg2(timestamp, s3) " + "values(8, 8.0)",
+ "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(9, false, 9, 9.0, 9)",
+ "INSERT INTO root.test.sg2(timestamp, s1) " + "values(9, true)",
+ "flush",
+ "INSERT INTO root.test.sg2(timestamp, s2) " + "values(9, 9)",
+ "INSERT INTO root.test.sg2(timestamp, s4) " + "values(9, 9)",
+ "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(10, true, 10, 10.0, 10)",
+ "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(10, true, 10, 10.0, 10)",
+ "flush",
+ "INSERT INTO root.test.sg3(time, s5, s6, s7, s8) aligned values(1, 1, true, 1.0, 1)",
+ "INSERT INTO root.test.sg3(time, s6, s7, s8) aligned values(2, false, 2.0, 2)",
+ "INSERT INTO root.test.sg3(time, s5, s7, s8) aligned values(3, 3, 3.0, 3)",
+ "INSERT INTO root.test.sg3(time, s5) aligned values(5, 5)",
+ "INSERT INTO root.test.sg3(time, s5, s8) aligned values(6, 6, 6)",
+ "INSERT INTO root.test.sg3(time, s6) aligned values(7, true)",
+ "INSERT INTO root.test.sg3(time, s5, s7) aligned values(8, 8, 8.0)",
+ "INSERT INTO root.test.sg3(time, s5, s7, s8) aligned values(9, 9, 9.0, 9)",
+ "INSERT INTO root.test.sg3(time, s7, s8) aligned values(10, 10.0, 10)",
+ "INSERT INTO root.test.sg5(timestamp, s1, s9) " + "values(1, true, 1)",
+ "flush",
+ };
+
+ private static long prevPartitionInterval;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ prevPartitionInterval = ConfigFactory.getConfig().getPartitionInterval();
+ ConfigFactory.getConfig().setPartitionInterval(1000);
+ EnvFactory.getEnv().initBeforeClass();
+ prepareData(SQLs);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanAfterClass();
+ ConfigFactory.getConfig().setPartitionInterval(prevPartitionInterval);
+ }
+
+ @Test
+ public void testUnsatisfiedRuleQuery() {
+ assertTestFail(
+ "select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1",
+ "416: When Having used with GroupByLevel: the suffix paths can only be measurement or one-level wildcard");
+
+ assertTestFail(
+ "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1",
+ "416: When Having used with GroupByLevel: the suffix paths can only be measurement or one-level wildcard");
+
+ assertTestFail(
+ "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) + s1 > 1",
+ "416: Raw data and aggregation result hybrid calculation is not supported");
+
+ assertTestFail(
+ "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having s1 + 1 > 1",
+ "416: Expression of HAVING clause must to be an Aggregation");
+ }
+
+ @Test
+ public void groupByTimeWithHavingTest() {
+ String[] expectedHeader = new String[] {TIMESTAMP_STR, count("root.test.sg1.s1")};
+ String[] retArray = new String[] {"1,1,", "5,1,", "9,2,"};
+ resultSetEqualWithDescOrderTest(
+ "select count(sg1.s1) from root.** "
+ + "GROUP BY ([1,11), 2ms) "
+ + "Having count(sg1.s2) > 1",
+ expectedHeader,
+ retArray);
+
+ resultSetEqualWithDescOrderTest(
+ "select count(sg1.s1) from root.**"
+ + "GROUP BY ([1,11), 2ms) "
+ + "Having count(sg1.s2) > 2",
+ expectedHeader,
+ new String[] {});
+ }
+
+ @Test
+ public void groupByTimeAlignByDeviceWithHavingTest() {
+ String[] expectedHeader = new String[] {TIMESTAMP_STR, "Device", count("s1"), count("s2")};
+ String[] retArray =
+ new String[] {
+ "1,root.test.sg1,1,2,",
+ "5,root.test.sg1,1,2,",
+ "9,root.test.sg1,2,2,",
+ "1,root.test.sg2,2,2,",
+ "5,root.test.sg2,1,2,",
+ "9,root.test.sg2,2,2,",
+ };
+ resultSetEqualTest(
+ "select count(s1), count(s2) from root.** "
+ + "GROUP BY ([1,11), 2ms) "
+ + "Having count(s2) > 1 "
+ + "Align by device",
+ expectedHeader,
+ retArray);
+
+ resultSetEqualTest(
+ "select count(s1), count(s2) from root.** "
+ + "GROUP BY ([1,11), 2ms) "
+ + "Having count(s2) > 2 "
+ + "Align by device",
+ expectedHeader,
+ new String[] {});
+ }
+
+ @Test
+ public void groupByLevelWithHavingTest() {
+ String[] expectedHeader =
+ new String[] {count("root.test.*.s1"), count("root.test.*.s2"), sum("root.test.*.s3")};
+ String[] retArray = new String[] {"14,14,87.0,"};
+ resultSetEqualWithDescOrderTest(
+ "select count(s1), count(s2), sum(s3) from root.** "
+ + "GROUP BY level=1 "
+ + "Having sum(s3) > 80",
+ expectedHeader,
+ retArray);
+
+ resultSetEqualWithDescOrderTest(
+ "select count(s1), count(s2), sum(s3) from root.** "
+ + "GROUP BY level=1 "
+ + "Having sum(s3) > 90",
+ expectedHeader,
+ new String[] {});
+ }
+
+ @Test
+ public void groupByTimeLevelWithHavingTest() {
+ String[] expectedHeader =
+ new String[] {
+ TIMESTAMP_STR, count("root.test.*.s1"), count("root.test.*.s2"),
+ };
+ String[] retArray =
+ new String[] {
+ "1,4,4,", "5,2,4,", "9,4,4,",
+ };
+ resultSetEqualWithDescOrderTest(
+ "select count(s1), count(s2) from root.** "
+ + "GROUP BY ([1,11),2ms), level=1 "
+ + "Having count(s2)>2",
+ expectedHeader,
+ retArray);
+
+ resultSetEqualWithDescOrderTest(
+ "select count(s1), count(s2) from root.** "
+ + "GROUP BY ([1,11),2ms), level=1 "
+ + "Having count(s2)>4",
+ expectedHeader,
+ new String[] {});
+ }
+
+ @Test
+ public void groupByTimeLevelWithHavingLimitTest() {
+ String[] expectedHeader =
+ new String[] {
+ TIMESTAMP_STR, count("root.test.*.s1"), count("root.test.*.s2"),
+ };
+ String[] retArray = new String[] {"5,2,4,"};
+ resultSetEqualTest(
+ "select count(s1), count(s2) from root.** "
+ + "GROUP BY ([1,11),2ms), level=1 "
+ + "Having count(s2) > 1 "
+ + "Limit 1 offset 1",
+ expectedHeader,
+ retArray);
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index 77799b4..6f9a65e 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
@@ -81,6 +81,8 @@
private Expression queryFilter;
+ private Expression havingExpression;
+
// map from grouped path name to list of input aggregation in `GROUP BY LEVEL` clause
private Map<Expression, Set<Expression>> groupByLevelExpressions;
@@ -108,6 +110,9 @@
// map from device name to query filter under this device
private Map<String, Expression> deviceToQueryFilter;
+ // map from device name to havingExpression under this device
+ private Map<String, Expression> deviceToHavingExpression;
+
// e.g. [s1,s2,s3] is query, but [s1, s3] exists in device1, then device1 -> [1, 3], s1 is 1 but
// not 0 because device is the first column
private Map<String, List<Integer>> deviceToMeasurementIndexesMap;
@@ -292,6 +297,22 @@
return groupByTimeParameter;
}
+ public Expression getHavingExpression() {
+ return havingExpression;
+ }
+
+ public void setHavingExpression(Expression havingExpression) {
+ this.havingExpression = havingExpression;
+ }
+
+ public Map<String, Expression> getDeviceToHavingExpression() {
+ return deviceToHavingExpression;
+ }
+
+ public void setDeviceToHavingExpression(Map<String, Expression> deviceTohavingExpression) {
+ this.deviceToHavingExpression = deviceTohavingExpression;
+ }
+
public void setGroupByTimeParameter(GroupByTimeParameter groupByTimeParameter) {
this.groupByTimeParameter = groupByTimeParameter;
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index 026820f..4927629 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
@@ -224,6 +224,7 @@
List<Pair<Expression, String>> outputExpressions;
if (queryStatement.isAlignByDevice()) {
Map<String, Set<Expression>> deviceToTransformExpressions = new HashMap<>();
+ Map<String, Set<Expression>> deviceToTransformExpressionsInHaving = new HashMap<>();
// all selected device
Set<PartialPath> deviceList = analyzeFrom(queryStatement, schemaTree);
@@ -237,6 +238,35 @@
deviceToTransformExpressions,
deviceToMeasurementsMap);
+ if (queryStatement.hasHaving()) {
+ List<PartialPath> measurementNotExistDevices = new ArrayList<>();
+ for (PartialPath device : deviceList) {
+ try {
+ deviceToTransformExpressionsInHaving.put(
+ device.toString(),
+ ExpressionAnalyzer.removeWildcardInFilterByDevice(
+ queryStatement.getHavingCondition().getPredicate(),
+ device,
+ schemaTree,
+ typeProvider,
+ false)
+ .stream()
+ .collect(Collectors.toSet()));
+ } catch (SemanticException e) {
+ if (e instanceof MeasurementNotExistException) {
+ logger.warn(e.getMessage());
+ measurementNotExistDevices.add(device);
+ continue;
+ }
+ throw e;
+ }
+ }
+ for (PartialPath measurementNotExistDevice : measurementNotExistDevices) {
+ deviceList.remove(measurementNotExistDevice);
+ deviceToTransformExpressions.remove(measurementNotExistDevice.getFullPath());
+ }
+ }
+
Map<String, List<Integer>> deviceToMeasurementIndexesMap = new HashMap<>();
List<String> allMeasurements =
outputExpressions.stream()
@@ -263,8 +293,12 @@
Map<String, Set<Expression>> deviceToAggregationExpressions = new HashMap<>();
Map<String, Set<Expression>> deviceToAggregationTransformExpressions = new HashMap<>();
+ Map<String, Expression> deviceToHavingExpression =
+ new HashMap<>(); // store filter of every device
for (String deviceName : deviceToTransformExpressions.keySet()) {
Set<Expression> transformExpressions = deviceToTransformExpressions.get(deviceName);
+ Set<Expression> transformExpressionsInHaving =
+ deviceToTransformExpressionsInHaving.get(deviceName);
Set<Expression> aggregationExpressions = new LinkedHashSet<>();
Set<Expression> aggregationTransformExpressions = new LinkedHashSet<>();
@@ -275,6 +309,25 @@
isHasRawDataInputAggregation =
analyzeAggregation(
transformExpressions, aggregationExpressions, aggregationTransformExpressions);
+
+ if (queryStatement.hasHaving() && transformExpressionsInHaving != null) {
+ List<Expression> aggregationExpressionsInHaving = new ArrayList<>();
+ isHasRawDataInputAggregation |=
+ analyzeAggregationInHaving(
+ transformExpressionsInHaving,
+ aggregationExpressionsInHaving,
+ aggregationExpressions,
+ aggregationTransformExpressions);
+
+ Expression havingExpression;
+ havingExpression =
+ analyzeHavingSplitByDevice(
+ transformExpressionsInHaving); // construct Filter from Having
+
+ havingExpression.inferTypes(typeProvider);
+ deviceToHavingExpression.put(deviceName, havingExpression);
+ }
+
deviceToAggregationExpressions.put(deviceName, aggregationExpressions);
deviceToAggregationTransformExpressions.put(
deviceName, aggregationTransformExpressions);
@@ -291,12 +344,23 @@
deviceToSourceExpressions.computeIfAbsent(deviceName, key -> new LinkedHashSet<>()),
isRawDataSource);
}
+
+ if (queryStatement.hasHaving() && transformExpressionsInHaving != null) {
+ for (Expression expression : transformExpressionsInHaving) {
+ updateSource(
+ expression,
+ deviceToSourceExpressions.computeIfAbsent(
+ deviceName, key -> new LinkedHashSet<>()),
+ isRawDataSource);
+ }
+ }
deviceToIsRawDataSource.put(deviceName, isRawDataSource);
}
analysis.setDeviceToAggregationExpressions(deviceToAggregationExpressions);
analysis.setDeviceToAggregationTransformExpressions(
deviceToAggregationTransformExpressions);
analysis.setDeviceToIsRawDataSource(deviceToIsRawDataSource);
+ analysis.setDeviceToHavingExpression(deviceToHavingExpression);
if (queryStatement.getWhereCondition() != null) {
Map<String, Expression> deviceToQueryFilter = new HashMap<>();
@@ -334,6 +398,20 @@
.map(Pair::getLeft)
.collect(Collectors.toCollection(LinkedHashSet::new));
+ // get removeWildcard Expressions in Having
+ // used to analyzeAggregation in Having expression and updateSource
+ Set<Expression> transformExpressionsInHaving =
+ queryStatement.hasHaving()
+ ? ExpressionAnalyzer.removeWildcardInFilter(
+ queryStatement.getHavingCondition().getPredicate(),
+ queryStatement.getFromComponent().getPrefixPaths(),
+ schemaTree,
+ typeProvider,
+ false)
+ .stream()
+ .collect(Collectors.toSet())
+ : null;
+
if (queryStatement.isGroupByLevel()) {
// map from grouped expression to set of input expressions
Map<Expression, Expression> rawPathToGroupedPathMap = new HashMap<>();
@@ -350,9 +428,28 @@
if (queryStatement.isAggregationQuery()) {
Set<Expression> aggregationExpressions = new HashSet<>();
Set<Expression> aggregationTransformExpressions = new HashSet<>();
+ List<Expression> aggregationExpressionsInHaving =
+ new ArrayList<>(); // as input of GroupByLevelController
isHasRawDataInputAggregation =
analyzeAggregation(
transformExpressions, aggregationExpressions, aggregationTransformExpressions);
+ if (queryStatement.hasHaving()) {
+ isHasRawDataInputAggregation |=
+ analyzeAggregationInHaving(
+ transformExpressionsInHaving,
+ aggregationExpressionsInHaving,
+ aggregationExpressions,
+ aggregationTransformExpressions);
+
+ Expression havingExpression = // construct Filter from Having
+ analyzeHaving(
+ queryStatement,
+ analysis.getGroupByLevelExpressions(),
+ transformExpressionsInHaving,
+ aggregationExpressionsInHaving);
+ havingExpression.inferTypes(typeProvider);
+ analysis.setHavingExpression(havingExpression);
+ }
analysis.setAggregationExpressions(aggregationExpressions);
analysis.setAggregationTransformExpressions(aggregationTransformExpressions);
}
@@ -368,6 +465,12 @@
updateSource(expression, sourceExpressions, isRawDataSource);
}
+ if (queryStatement.hasHaving()) {
+ for (Expression expression : transformExpressionsInHaving) {
+ updateSource(expression, sourceExpressions, isRawDataSource);
+ }
+ }
+
if (queryStatement.getWhereCondition() != null) {
Expression queryFilter = analyzeWhere(queryStatement, schemaTree);
@@ -648,13 +751,40 @@
return isHasRawDataInputAggregation;
}
+ private boolean analyzeAggregationInHaving(
+ Set<Expression> expressions,
+ List<Expression> aggregationExpressionsInHaving,
+ Set<Expression> aggregationExpressions,
+ Set<Expression> aggregationTransformExpressions) {
+ // true if nested expressions and UDFs exist in aggregation function
+ // i.e. select sum(s1 + 1) from root.sg.d1 align by device
+ boolean isHasRawDataInputAggregation = false;
+ for (Expression expression : expressions) {
+ for (Expression aggregationExpression :
+ ExpressionAnalyzer.searchAggregationExpressions(expression)) {
+ aggregationExpressionsInHaving.add(aggregationExpression);
+ aggregationExpressions.add(aggregationExpression);
+ aggregationTransformExpressions.addAll(aggregationExpression.getExpressions());
+ }
+ }
+
+ for (Expression aggregationTransformExpression : aggregationTransformExpressions) {
+ if (ExpressionAnalyzer.checkIsNeedTransform(aggregationTransformExpression)) {
+ isHasRawDataInputAggregation = true;
+ break;
+ }
+ }
+ return isHasRawDataInputAggregation;
+ }
+
private Expression analyzeWhere(QueryStatement queryStatement, ISchemaTree schemaTree) {
List<Expression> rewrittenPredicates =
- ExpressionAnalyzer.removeWildcardInQueryFilter(
+ ExpressionAnalyzer.removeWildcardInFilter(
queryStatement.getWhereCondition().getPredicate(),
queryStatement.getFromComponent().getPrefixPaths(),
schemaTree,
- typeProvider);
+ typeProvider,
+ true);
return ExpressionUtils.constructQueryFilter(
rewrittenPredicates.stream().distinct().collect(Collectors.toList()));
}
@@ -662,15 +792,60 @@
private Expression analyzeWhereSplitByDevice(
QueryStatement queryStatement, PartialPath devicePath, ISchemaTree schemaTree) {
List<Expression> rewrittenPredicates =
- ExpressionAnalyzer.removeWildcardInQueryFilterByDevice(
+ ExpressionAnalyzer.removeWildcardInFilterByDevice(
queryStatement.getWhereCondition().getPredicate(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ true);
return ExpressionUtils.constructQueryFilter(
rewrittenPredicates.stream().distinct().collect(Collectors.toList()));
}
+ private Expression analyzeHaving(
+ QueryStatement queryStatement,
+ Map<Expression, Set<Expression>> groupByLevelExpressions,
+ Set<Expression> transformExpressionsInHaving,
+ List<Expression> aggregationExpressionsInHaving) {
+
+ if (queryStatement.isGroupByLevel()) {
+ Map<Expression, Expression> RawPathToGroupedPathMapInHaving =
+ analyzeGroupByLevelInHaving(
+ queryStatement, aggregationExpressionsInHaving, groupByLevelExpressions);
+ List<Expression> convertedPredicates = new ArrayList<>();
+ for (Expression expression : transformExpressionsInHaving) {
+ convertedPredicates.add(
+ ExpressionAnalyzer.replaceRawPathWithGroupedPath(
+ expression, RawPathToGroupedPathMapInHaving));
+ }
+ return ExpressionUtils.constructQueryFilter(
+ convertedPredicates.stream().distinct().collect(Collectors.toList()));
+ }
+
+ return ExpressionUtils.constructQueryFilter(
+ transformExpressionsInHaving.stream().distinct().collect(Collectors.toList()));
+ }
+
+ private Expression analyzeHavingSplitByDevice(Set<Expression> transformExpressionsInHaving) {
+ return ExpressionUtils.constructQueryFilter(
+ transformExpressionsInHaving.stream().distinct().collect(Collectors.toList()));
+ }
+
+ private Map<Expression, Expression> analyzeGroupByLevelInHaving(
+ QueryStatement queryStatement,
+ List<Expression> inputExpressions,
+ Map<Expression, Set<Expression>> groupByLevelExpressions) {
+ GroupByLevelController groupByLevelController =
+ new GroupByLevelController(
+ queryStatement.getGroupByLevelComponent().getLevels(), typeProvider);
+ for (Expression inputExpression : inputExpressions) {
+ groupByLevelController.control(false, inputExpression, null);
+ }
+ Map<Expression, Set<Expression>> groupedPathMap = groupByLevelController.getGroupedPathMap();
+ groupByLevelExpressions.putAll(groupedPathMap);
+ return groupByLevelController.getRawPathToGroupedPathMap();
+ }
+
private Map<Expression, Set<Expression>> analyzeGroupByLevel(
QueryStatement queryStatement,
List<Pair<Expression, String>> outputExpressions,
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
index f4ee8a2..ed0ed49 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
@@ -72,6 +72,13 @@
ExpressionAnalyzer.constructPatternTreeFromExpression(
queryStatement.getWhereCondition().getPredicate(), prefixPaths, patternTree);
}
+
+ // concat HAVING with FROM
+ if (queryStatement.getHavingCondition() != null) {
+ ExpressionAnalyzer.constructPatternTreeFromExpression(
+ queryStatement.getHavingCondition().getPredicate(), prefixPaths, patternTree);
+ }
+
return queryStatement;
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
index aa35eef..9c46f16 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
@@ -50,6 +50,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.cartesianProduct;
@@ -58,17 +59,21 @@
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeFirst;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeSecond;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeThird;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpression;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructFunctionExpression;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructFunctionExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpression;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTimeSeriesOperand;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTimeSeriesOperands;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpression;
import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpressions;
public class ExpressionAnalyzer {
-
/**
- * Check if all suffix paths in expression are measurements or one-level wildcards in ALIGN BY
- * DEVICE query. If not, throw a {@link SemanticException}.
+ * Check if all suffix paths in expression are measurements or one-level wildcards, used in ALIGN
+ * BY DEVICE query or GroupByLevel query. If not, throw a {@link SemanticException}.
*
* @param expression expression to be checked
*/
@@ -91,7 +96,7 @@
if (path.getNodes().length > 1
|| path.getFullPath().equals(IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD)) {
throw new SemanticException(
- "ALIGN BY DEVICE: the suffix paths can only be measurement or one-level wildcard");
+ "the suffix paths can only be measurement or one-level wildcard");
}
} else if (expression instanceof TimestampOperand || expression instanceof ConstantOperand) {
// do nothing
@@ -395,53 +400,60 @@
}
/**
- * Concat suffix path in WHERE clause with the prefix path in the FROM clause. And then, bind
- * schema ({@link PartialPath} -> {@link MeasurementPath}) and removes wildcards in Expression.
+ * Concat suffix path in WHERE and HAVING clause with the prefix path in the FROM clause. And
+ * then, bind schema ({@link PartialPath} -> {@link MeasurementPath}) and removes wildcards in
+ * Expression.
*
* @param prefixPaths prefix paths in the FROM clause
* @param schemaTree interface for querying schema information
* @param typeProvider a map to record output symbols and their data types
* @return the expression list with full path and after binding schema
*/
- public static List<Expression> removeWildcardInQueryFilter(
+ public static List<Expression> removeWildcardInFilter(
Expression predicate,
List<PartialPath> prefixPaths,
ISchemaTree schemaTree,
- TypeProvider typeProvider) {
+ TypeProvider typeProvider,
+ boolean isWhere) {
if (predicate instanceof TernaryExpression) {
List<Expression> firstExpressions =
- removeWildcardInQueryFilter(
+ removeWildcardInFilter(
((TernaryExpression) predicate).getFirstExpression(),
prefixPaths,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> secondExpressions =
- removeWildcardInQueryFilter(
+ removeWildcardInFilter(
((TernaryExpression) predicate).getSecondExpression(),
prefixPaths,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> thirdExpressions =
- removeWildcardInQueryFilter(
+ removeWildcardInFilter(
((TernaryExpression) predicate).getThirdExpression(),
prefixPaths,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
return reconstructTernaryExpressions(
predicate, firstExpressions, secondExpressions, thirdExpressions);
} else if (predicate instanceof BinaryExpression) {
List<Expression> leftExpressions =
- removeWildcardInQueryFilter(
+ removeWildcardInFilter(
((BinaryExpression) predicate).getLeftExpression(),
prefixPaths,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> rightExpressions =
- removeWildcardInQueryFilter(
+ removeWildcardInFilter(
((BinaryExpression) predicate).getRightExpression(),
prefixPaths,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
if (predicate.getExpressionType() == ExpressionType.LOGIC_AND) {
List<Expression> resultExpressions = new ArrayList<>(leftExpressions);
resultExpressions.addAll(rightExpressions);
@@ -451,17 +463,22 @@
predicate.getExpressionType(), leftExpressions, rightExpressions);
} else if (predicate instanceof UnaryExpression) {
List<Expression> childExpressions =
- removeWildcardInQueryFilter(
- ((UnaryExpression) predicate).getExpression(), prefixPaths, schemaTree, typeProvider);
+ removeWildcardInFilter(
+ ((UnaryExpression) predicate).getExpression(),
+ prefixPaths,
+ schemaTree,
+ typeProvider,
+ isWhere);
return reconstructUnaryExpressions((UnaryExpression) predicate, childExpressions);
} else if (predicate instanceof FunctionExpression) {
- if (predicate.isBuiltInAggregationFunctionExpression()) {
+ if (predicate.isBuiltInAggregationFunctionExpression() && isWhere) {
throw new SemanticException("aggregate functions are not supported in WHERE clause");
}
List<List<Expression>> extendedExpressions = new ArrayList<>();
for (Expression suffixExpression : predicate.getExpressions()) {
extendedExpressions.add(
- removeWildcardInQueryFilter(suffixExpression, prefixPaths, schemaTree, typeProvider));
+ removeWildcardInFilter(
+ suffixExpression, prefixPaths, schemaTree, typeProvider, isWhere));
}
List<List<Expression>> childExpressionsList = new ArrayList<>();
cartesianProduct(extendedExpressions, childExpressionsList, 0, new ArrayList<>());
@@ -481,7 +498,9 @@
List<MeasurementPath> actualPaths = schemaTree.searchMeasurementPaths(concatPath).left;
if (actualPaths.size() == 0) {
throw new SemanticException(
- String.format("the path '%s' in WHERE clause does not exist", concatPath));
+ String.format(
+ "the path '%s' in %s clause does not exist",
+ concatPath, isWhere ? "WHERE" : "HAVING"));
}
noStarPaths.addAll(actualPaths);
}
@@ -498,6 +517,57 @@
}
}
+ public static Expression replaceRawPathWithGroupedPath(
+ Expression predicate, Map<Expression, Expression> rawPathToGroupedPathMapInHaving) {
+ if (predicate instanceof TernaryExpression) {
+ Expression firstExpression =
+ replaceRawPathWithGroupedPath(
+ ((TernaryExpression) predicate).getFirstExpression(),
+ rawPathToGroupedPathMapInHaving);
+ Expression secondExpression =
+ replaceRawPathWithGroupedPath(
+ ((TernaryExpression) predicate).getSecondExpression(),
+ rawPathToGroupedPathMapInHaving);
+ Expression thirdExpression =
+ replaceRawPathWithGroupedPath(
+ ((TernaryExpression) predicate).getThirdExpression(),
+ rawPathToGroupedPathMapInHaving);
+ return reconstructTernaryExpression(
+ predicate, firstExpression, secondExpression, thirdExpression);
+ } else if (predicate instanceof BinaryExpression) {
+ Expression leftExpression =
+ replaceRawPathWithGroupedPath(
+ ((BinaryExpression) predicate).getLeftExpression(), rawPathToGroupedPathMapInHaving);
+ Expression rightExpression =
+ replaceRawPathWithGroupedPath(
+ ((BinaryExpression) predicate).getRightExpression(), rawPathToGroupedPathMapInHaving);
+ return reconstructBinaryExpression(
+ predicate.getExpressionType(), leftExpression, rightExpression);
+ } else if (predicate instanceof UnaryExpression) {
+ Expression expression =
+ replaceRawPathWithGroupedPath(
+ ((UnaryExpression) predicate).getExpression(), rawPathToGroupedPathMapInHaving);
+ return reconstructUnaryExpression((UnaryExpression) predicate, expression);
+ } else if (predicate instanceof FunctionExpression) {
+ List<Expression> expressions = predicate.getExpressions();
+ List<Expression> childrenExpressions = new ArrayList<>();
+ for (Expression expression : expressions) {
+ childrenExpressions.add(
+ replaceRawPathWithGroupedPath(expression, rawPathToGroupedPathMapInHaving));
+ }
+ return reconstructFunctionExpression((FunctionExpression) predicate, childrenExpressions);
+ } else if (predicate instanceof TimeSeriesOperand) {
+ PartialPath groupedPath =
+ ((TimeSeriesOperand) rawPathToGroupedPathMapInHaving.get(predicate)).getPath();
+ return reconstructTimeSeriesOperand(groupedPath);
+ } else if (predicate instanceof TimestampOperand || predicate instanceof ConstantOperand) {
+ return predicate;
+ } else {
+ throw new IllegalArgumentException(
+ "unsupported expression type: " + predicate.getExpressionType());
+ }
+ }
+
/**
* Concat expression with the device path in the FROM clause.And then, bind schema ({@link
* PartialPath} -> {@link MeasurementPath}) and removes wildcards in Expression. This method used
@@ -587,50 +657,56 @@
}
/**
- * Concat measurement in WHERE clause with device path. And then, bind schema ({@link PartialPath}
- * -> {@link MeasurementPath}) and removes wildcards.
+ * Concat measurement in WHERE and HAVING clause with device path. And then, bind schema ({@link
+ * PartialPath} -> {@link MeasurementPath}) and removes wildcards.
*
* @return the expression list with full path and after binding schema
*/
- public static List<Expression> removeWildcardInQueryFilterByDevice(
+ public static List<Expression> removeWildcardInFilterByDevice(
Expression predicate,
PartialPath devicePath,
ISchemaTree schemaTree,
- TypeProvider typeProvider) {
+ TypeProvider typeProvider,
+ boolean isWhere) {
if (predicate instanceof TernaryExpression) {
List<Expression> firstExpressions =
- removeWildcardInQueryFilterByDevice(
+ removeWildcardInFilterByDevice(
((TernaryExpression) predicate).getFirstExpression(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> secondExpressions =
- removeWildcardInQueryFilterByDevice(
+ removeWildcardInFilterByDevice(
((TernaryExpression) predicate).getSecondExpression(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> thirdExpressions =
- removeWildcardInQueryFilterByDevice(
+ removeWildcardInFilterByDevice(
((TernaryExpression) predicate).getThirdExpression(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
return reconstructTernaryExpressions(
predicate, firstExpressions, secondExpressions, thirdExpressions);
} else if (predicate instanceof BinaryExpression) {
List<Expression> leftExpressions =
- removeWildcardInQueryFilterByDevice(
+ removeWildcardInFilterByDevice(
((BinaryExpression) predicate).getLeftExpression(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
List<Expression> rightExpressions =
- removeWildcardInQueryFilterByDevice(
+ removeWildcardInFilterByDevice(
((BinaryExpression) predicate).getRightExpression(),
devicePath,
schemaTree,
- typeProvider);
+ typeProvider,
+ isWhere);
if (predicate.getExpressionType() == ExpressionType.LOGIC_AND) {
List<Expression> resultExpressions = new ArrayList<>(leftExpressions);
resultExpressions.addAll(rightExpressions);
@@ -640,18 +716,22 @@
predicate.getExpressionType(), leftExpressions, rightExpressions);
} else if (predicate instanceof UnaryExpression) {
List<Expression> childExpressions =
- removeWildcardInQueryFilterByDevice(
- ((UnaryExpression) predicate).getExpression(), devicePath, schemaTree, typeProvider);
+ removeWildcardInFilterByDevice(
+ ((UnaryExpression) predicate).getExpression(),
+ devicePath,
+ schemaTree,
+ typeProvider,
+ isWhere);
return reconstructUnaryExpressions((UnaryExpression) predicate, childExpressions);
} else if (predicate instanceof FunctionExpression) {
- if (predicate.isBuiltInAggregationFunctionExpression()) {
+ if (predicate.isBuiltInAggregationFunctionExpression() && isWhere) {
throw new SemanticException("aggregate functions are not supported in WHERE clause");
}
List<List<Expression>> extendedExpressions = new ArrayList<>();
for (Expression suffixExpression : predicate.getExpressions()) {
extendedExpressions.add(
- removeWildcardInQueryFilterByDevice(
- suffixExpression, devicePath, schemaTree, typeProvider));
+ removeWildcardInFilterByDevice(
+ suffixExpression, devicePath, schemaTree, typeProvider, isWhere));
}
List<List<Expression>> childExpressionsList = new ArrayList<>();
cartesianProduct(extendedExpressions, childExpressionsList, 0, new ArrayList<>());
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
index f1b0df1..62df923 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
@@ -66,6 +66,10 @@
return resultExpressions;
}
+ public static Expression reconstructTimeSeriesOperand(PartialPath actualPath) {
+ return new TimeSeriesOperand(actualPath);
+ }
+
public static List<Expression> reconstructFunctionExpressions(
FunctionExpression expression, List<List<Expression>> childExpressionsList) {
List<Expression> resultExpressions = new ArrayList<>();
@@ -79,6 +83,12 @@
return resultExpressions;
}
+ public static Expression reconstructFunctionExpression(
+ FunctionExpression expression, List<Expression> childExpressions) {
+ return new FunctionExpression(
+ expression.getFunctionName(), expression.getFunctionAttributes(), childExpressions);
+ }
+
public static List<Expression> reconstructUnaryExpressions(
UnaryExpression expression, List<Expression> childExpressions) {
List<Expression> resultExpressions = new ArrayList<>();
@@ -123,6 +133,39 @@
return resultExpressions;
}
+ public static Expression reconstructUnaryExpression(
+ UnaryExpression expression, Expression childExpression) {
+ switch (expression.getExpressionType()) {
+ case IS_NULL:
+ return new IsNullExpression(childExpression, ((IsNullExpression) expression).isNot());
+ case IN:
+ return new InExpression(
+ childExpression,
+ ((InExpression) expression).isNotIn(),
+ ((InExpression) expression).getValues());
+ case LIKE:
+ return new LikeExpression(
+ childExpression,
+ ((LikeExpression) expression).getPatternString(),
+ ((LikeExpression) expression).getPattern());
+ case LOGIC_NOT:
+ return new LogicNotExpression(childExpression);
+
+ case NEGATION:
+ return new NegationExpression(childExpression);
+
+ case REGEXP:
+ return new RegularExpression(
+ childExpression,
+ ((RegularExpression) expression).getPatternString(),
+ ((RegularExpression) expression).getPattern());
+
+ default:
+ throw new IllegalArgumentException(
+ "unsupported expression type: " + expression.getExpressionType());
+ }
+ }
+
public static List<Expression> reconstructBinaryExpressions(
ExpressionType expressionType,
List<Expression> leftExpressions,
@@ -178,6 +221,49 @@
return resultExpressions;
}
+ public static Expression reconstructBinaryExpression(
+ ExpressionType expressionType, Expression leftExpression, Expression rightExpression) {
+ switch (expressionType) {
+ case ADDITION:
+ return new AdditionExpression(leftExpression, rightExpression);
+
+ case SUBTRACTION:
+ return new SubtractionExpression(leftExpression, rightExpression);
+ case MULTIPLICATION:
+ return new MultiplicationExpression(leftExpression, rightExpression);
+ case DIVISION:
+ return new DivisionExpression(leftExpression, rightExpression);
+ case MODULO:
+ return new ModuloExpression(leftExpression, rightExpression);
+
+ case LESS_THAN:
+ return new LessThanExpression(leftExpression, rightExpression);
+ case LESS_EQUAL:
+ return new LessEqualExpression(leftExpression, rightExpression);
+
+ case GREATER_THAN:
+ return new GreaterThanExpression(leftExpression, rightExpression);
+
+ case GREATER_EQUAL:
+ return new GreaterEqualExpression(leftExpression, rightExpression);
+
+ case EQUAL_TO:
+ return new EqualToExpression(leftExpression, rightExpression);
+
+ case NON_EQUAL:
+ return new NonEqualExpression(leftExpression, rightExpression);
+
+ case LOGIC_AND:
+ return new LogicAndExpression(leftExpression, rightExpression);
+
+ case LOGIC_OR:
+ return new LogicOrExpression(leftExpression, rightExpression);
+
+ default:
+ throw new IllegalArgumentException("unsupported expression type: " + expressionType);
+ }
+ }
+
public static List<Expression> reconstructTernaryExpressions(
Expression expression,
List<Expression> firstExpressions,
@@ -194,13 +280,32 @@
fe, se, te, ((BetweenExpression) expression).isNotBetween()));
break;
default:
- throw new UnsupportedOperationException();
+ throw new IllegalArgumentException(
+ "unsupported expression type: " + expression.getExpressionType());
}
}
}
return resultExpressions;
}
+ public static Expression reconstructTernaryExpression(
+ Expression expression,
+ Expression firstExpression,
+ Expression secondExpression,
+ Expression thirdExpression) {
+ switch (expression.getExpressionType()) {
+ case BETWEEN:
+ return new BetweenExpression(
+ firstExpression,
+ secondExpression,
+ thirdExpression,
+ ((BetweenExpression) expression).isNotBetween());
+ default:
+ throw new IllegalArgumentException(
+ "unsupported expression type: " + expression.getExpressionType());
+ }
+ }
+
public static <T> void cartesianProduct(
List<List<T>> dimensionValue, List<List<T>> resultList, int layer, List<T> currentList) {
if (layer < dimensionValue.size() - 1) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
index 2371300..259b505 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
@@ -432,7 +432,7 @@
@Override
public boolean isMappable(TypeProvider typeProvider) {
if (isBuiltInAggregationFunctionExpression) {
- return false;
+ return true;
}
return new UDTFInformationInferrer(functionName)
.getAccessStrategy(
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index e0c6bf0..1e444de 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -65,6 +65,7 @@
import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -827,6 +828,11 @@
parseOrderByClause(ctx.orderByClause());
}
+ // parse Having
+ if (ctx.havingClause() != null) {
+ parseHavingClause(ctx.havingClause());
+ }
+
// parse limit & offset
if (ctx.specialLimit() != null) {
return visit(ctx.specialLimit());
@@ -845,6 +851,11 @@
parseOrderByClause(ctx.orderByClause());
}
+ // parse Having
+ if (ctx.havingClause() != null) {
+ parseHavingClause(ctx.havingClause());
+ }
+
// parse limit & offset
if (ctx.specialLimit() != null) {
return visit(ctx.specialLimit());
@@ -975,6 +986,11 @@
parseOrderByClause(ctx.orderByClause());
}
+ // parse Having
+ if (ctx.havingClause() != null) {
+ parseHavingClause(ctx.havingClause());
+ }
+
// parse limit & offset
if (ctx.specialLimit() != null) {
return visit(ctx.specialLimit());
@@ -998,6 +1014,13 @@
queryStatement.setGroupByLevelComponent(groupByLevelComponent);
}
+ // HAVING Clause
+ public void parseHavingClause(IoTDBSqlParser.HavingClauseContext ctx) {
+ Expression predicate =
+ parseExpression(ctx.expression(), ctx.expression().OPERATOR_NOT() == null);
+ queryStatement.setHavingCondition(new HavingCondition(predicate));
+ }
+
// Fill Clause
@Override
public Statement visitFillStatement(IoTDBSqlParser.FillStatementContext ctx) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
index ad5cbbe..8dda44b 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
@@ -118,6 +118,9 @@
analysis.getDeviceToQueryFilter() != null
? analysis.getDeviceToQueryFilter().get(deviceName)
: null,
+ analysis.getDeviceToHavingExpression() != null
+ ? analysis.getDeviceToHavingExpression().get(deviceName)
+ : null,
analysis.getDeviceToMeasurementIndexesMap().get(deviceName),
context));
deviceToSubPlanMap.put(deviceName, subPlanBuilder.getRoot());
@@ -142,6 +145,7 @@
analysis.getAggregationTransformExpressions(),
analysis.getTransformExpressions(),
analysis.getQueryFilter(),
+ analysis.getHavingExpression(),
null,
context));
}
@@ -164,6 +168,7 @@
Set<Expression> aggregationTransformExpressions,
Set<Expression> transformExpressions,
Expression queryFilter,
+ Expression havingExpression,
List<Integer> measurementIndexes, // only used in ALIGN BY DEVICE
MPPQueryContext context) {
LogicalPlanBuilder planBuilder = new LogicalPlanBuilder(context);
@@ -232,12 +237,32 @@
}
}
- planBuilder =
- planBuilder.planTransform(
- transformExpressions,
- queryStatement.isGroupByTime(),
- queryStatement.getSelectComponent().getZoneId(),
- queryStatement.getResultTimeOrder());
+ if (queryStatement.isGroupByLevel()) {
+ planBuilder = // plan Having with GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
+ analysis.getGroupByLevelExpressions().keySet(),
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ } else {
+ if (havingExpression != null) {
+ planBuilder = // plan Having without GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
+ transformExpressions,
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ } else {
+ planBuilder = // no Having
+ planBuilder.planTransform(
+ transformExpressions,
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ }
+ }
} else {
if (analysis.hasValueFilter()) {
planBuilder =
@@ -284,23 +309,61 @@
measurementIndexes,
analysis.getGroupByLevelExpressions(),
analysis.getTypeProvider());
+ if (queryStatement.isGroupByLevel()) {
+ planBuilder = // plan Having with GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
+ analysis.getGroupByLevelExpressions().keySet(),
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ } else {
+ planBuilder = // plan Having without GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
+ transformExpressions,
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ }
} else {
planBuilder =
- planBuilder
- .planAggregationSource(
- sourceExpressions,
- curStep,
- queryStatement.getResultTimeOrder(),
- analysis.getGlobalTimeFilter(),
- analysis.getGroupByTimeParameter(),
- aggregationExpressions,
- analysis.getGroupByLevelExpressions(),
- analysis.getTypeProvider())
- .planTransform(
+ planBuilder.planAggregationSource(
+ sourceExpressions,
+ curStep,
+ queryStatement.getResultTimeOrder(),
+ analysis.getGlobalTimeFilter(),
+ analysis.getGroupByTimeParameter(),
+ aggregationExpressions,
+ analysis.getGroupByLevelExpressions(),
+ analysis.getTypeProvider());
+
+ if (queryStatement.isGroupByLevel()) {
+ planBuilder = // plan Having with GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
+ analysis.getGroupByLevelExpressions().keySet(),
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ } else {
+ if (havingExpression != null) {
+ planBuilder = // plan Having without GroupByLevel
+ planBuilder.planFilterAndTransform(
+ havingExpression,
transformExpressions,
queryStatement.isGroupByTime(),
queryStatement.getSelectComponent().getZoneId(),
queryStatement.getResultTimeOrder());
+ } else {
+ planBuilder = // no Having
+ planBuilder.planTransform(
+ transformExpressions,
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ }
+ }
}
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java
new file mode 100644
index 0000000..c9a89e3
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java
@@ -0,0 +1,42 @@
+/*
+ * 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.mpp.plan.statement.component;
+
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
+
+/** This class maintains information of {@code HAVING} clause. */
+public class HavingCondition extends StatementNode {
+ private Expression predicate;
+
+ public HavingCondition() {}
+
+ public HavingCondition(Expression predicate) {
+ this.predicate = predicate;
+ }
+
+ public Expression getPredicate() {
+ return predicate;
+ }
+
+ public void setPredicate(Expression predicate) {
+ this.predicate = predicate;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
index 0ab276b..0b06d17 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
@@ -31,6 +31,7 @@
import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -66,6 +67,7 @@
protected SelectComponent selectComponent;
protected FromComponent fromComponent;
protected WhereCondition whereCondition;
+ protected HavingCondition havingCondition;
// row limit and offset for result set. The default value is 0, which means no limit
protected int rowLimit = 0;
@@ -122,6 +124,18 @@
this.whereCondition = whereCondition;
}
+ public boolean hasHaving() {
+ return havingCondition != null;
+ }
+
+ public HavingCondition getHavingCondition() {
+ return havingCondition;
+ }
+
+ public void setHavingCondition(HavingCondition havingCondition) {
+ this.havingCondition = havingCondition;
+ }
+
public int getRowLimit() {
return rowLimit;
}
@@ -264,14 +278,37 @@
}
}
+ if (getHavingCondition() != null) {
+ Expression havingExpression = getHavingCondition().getPredicate();
+ if (ExpressionAnalyzer.identifyOutputColumnType(havingExpression, true)
+ != ResultColumn.ColumnType.AGGREGATION) {
+ throw new SemanticException("Expression of HAVING clause must to be an Aggregation");
+ }
+ try {
+ if (isGroupByLevel()) { // check path in SELECT and HAVING only have one node
+ for (ResultColumn resultColumn : getSelectComponent().getResultColumns()) {
+ ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+ }
+ ExpressionAnalyzer.checkIsAllMeasurement(havingExpression);
+ }
+ } catch (SemanticException e) {
+ throw new SemanticException("When Having used with GroupByLevel: " + e.getMessage());
+ }
+ }
+
if (isAlignByDevice()) {
// the paths can only be measurement or one-level wildcard in ALIGN BY DEVICE
- for (ResultColumn resultColumn : selectComponent.getResultColumns()) {
- ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+ try {
+ for (ResultColumn resultColumn : selectComponent.getResultColumns()) {
+ ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+ }
+ if (getWhereCondition() != null) {
+ ExpressionAnalyzer.checkIsAllMeasurement(getWhereCondition().getPredicate());
+ }
+ } catch (SemanticException e) {
+ throw new SemanticException("ALIGN BY DEVICE: " + e.getMessage());
}
- if (getWhereCondition() != null) {
- ExpressionAnalyzer.checkIsAllMeasurement(getWhereCondition().getPredicate());
- }
+
if (isOrderByTimeseries()) {
throw new SemanticException("Sorting by timeseries is only supported in last queries.");
}