You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2023/04/27 14:49:25 UTC
[iotdb] branch master updated: [IOTDB-5765] Support ORDER BY expression [FE Part]
This is an automated email from the ASF dual-hosted git repository.
jackietien 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 ba4dea6306 [IOTDB-5765] Support ORDER BY expression [FE Part]
ba4dea6306 is described below
commit ba4dea630696efa88bd3a3e3e0300c64febac75a
Author: YangCaiyin <yc...@gmail.com>
AuthorDate: Thu Apr 27 22:49:18 2023 +0800
[IOTDB-5765] Support ORDER BY expression [FE Part]
---
.../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 | 1 +
.../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 | 8 +
.../apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java | 1224 ++++++++++++++++++++
.../execution/operator/process/SortOperator.java | 28 +-
.../process/join/merge/MergeSortComparator.java | 178 +--
.../process/join/merge/MergeSortKeyComparator.java | 53 +
.../apache/iotdb/db/mpp/plan/analyze/Analysis.java | 49 +
.../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java | 133 ++-
.../db/mpp/plan/analyze/ConcatPathRewriter.java | 22 +-
.../visitor/ColumnTransformerVisitor.java | 38 +-
.../iotdb/db/mpp/plan/parser/ASTVisitor.java | 44 +-
.../db/mpp/plan/planner/LogicalPlanBuilder.java | 143 ++-
.../db/mpp/plan/planner/LogicalPlanVisitor.java | 19 +-
.../db/mpp/plan/planner/OperatorTreeGenerator.java | 12 +-
.../planner/distribution/DistributionPlanner.java | 5 +-
.../planner/distribution/ExchangeNodeAdder.java | 6 +
.../plan/planner/distribution/SourceRewriter.java | 37 +
.../component/{SortKey.java => NullOrdering.java} | 19 +-
.../plan/statement/component/OrderByComponent.java | 56 +-
.../db/mpp/plan/statement/component/SortItem.java | 61 +-
.../db/mpp/plan/statement/component/SortKey.java | 16 +-
.../db/mpp/plan/statement/crud/QueryStatement.java | 80 ++
.../execution/operator/MergeSortOperatorTest.java | 20 +-
.../iotdb/db/mpp/plan/analyze/AnalyzeTest.java | 200 ++++
.../tsfile/file/metadata/enums/TSDataType.java | 17 +
25 files changed, 2184 insertions(+), 285 deletions(-)
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 0f4df03ce7..d88e4533c6 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
@@ -623,6 +623,7 @@ orderByClause
orderByAttributeClause
: sortKey (DESC | ASC)?
+ | expression (DESC | ASC)? (NULLS (FIRST|LAST))?
;
sortKey
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 d9eefda093..fb7c5af931 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
@@ -270,6 +270,10 @@ FILE
: F I L E
;
+FIRST
+ : F I R S T
+ ;
+
FLUSH
: F L U S H
;
@@ -414,6 +418,10 @@ NOW
: N O W
;
+NULLS
+ : N U L L S
+ ;
+
OF
: O F
;
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java
new file mode 100644
index 0000000000..a39b965a18
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java
@@ -0,0 +1,1224 @@
+/*
+ * 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.orderBy;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+
+import org.bouncycastle.util.Arrays;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBOrderByIT {
+
+ // the data can be viewed in
+ // https://docs.google.com/spreadsheets/d/1OWA1bKraArCwWVnuTjuhJ5yLG0PFLdD78gD6FjquepI/edit#gid=0
+ private static final String[] sql =
+ new String[] {
+ "CREATE DATABASE root.sg",
+ "CREATE TIMESERIES root.sg.d.num WITH DATATYPE=INT32, ENCODING=RLE",
+ "CREATE TIMESERIES root.sg.d.bigNum WITH DATATYPE=INT64, ENCODING=RLE",
+ "CREATE TIMESERIES root.sg.d.floatNum WITH DATATYPE=DOUBLE, ENCODING=RLE, 'MAX_POINT_NUMBER'='5'",
+ "CREATE TIMESERIES root.sg.d.str WITH DATATYPE=TEXT, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.d.bool WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(0,3,2947483648,231.2121,\"coconut\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(20,2,2147483648,434.12,\"pineapple\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(40,1,2247483648,12.123,\"apricot\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(80,9,2147483646,43.12,\"apple\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(100,8,2147483964,4654.231,\"papaya\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000000,6,2147483650,1231.21,\"banana\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000100,10,3147483648,231.55,\"pumelo\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000500,4,2147493648,213.1,\"peach\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536001000,5,2149783648,56.32,\"orange\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536010000,7,2147983648,213.112,\"lemon\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536100000,11,2147468648,54.121,\"pitaya\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536000000,12,2146483648,45.231,\"strawberry\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536000020,14,2907483648,231.34,\"cherry\",FALSE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536900000,13,2107483648,54.12,\"lychee\",TRUE)",
+ "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(51536000000,15,3147483648,235.213,\"watermelon\",TRUE)"
+ };
+
+ private static final String[] sql2 =
+ new String[] {
+ "CREATE TIMESERIES root.sg.d2.num WITH DATATYPE=INT32, ENCODING=RLE",
+ "CREATE TIMESERIES root.sg.d2.bigNum WITH DATATYPE=INT64, ENCODING=RLE",
+ "CREATE TIMESERIES root.sg.d2.floatNum WITH DATATYPE=DOUBLE, ENCODING=RLE, 'MAX_POINT_NUMBER'='5'",
+ "CREATE TIMESERIES root.sg.d2.str WITH DATATYPE=TEXT, ENCODING=PLAIN",
+ "CREATE TIMESERIES root.sg.d2.bool WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(0,3,2947483648,231.2121,\"coconut\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(20,2,2147483648,434.12,\"pineapple\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(40,1,2247483648,12.123,\"apricot\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(80,9,2147483646,43.12,\"apple\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(100,8,2147483964,4654.231,\"papaya\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000000,6,2147483650,1231.21,\"banana\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000100,10,3147483648,231.55,\"pumelo\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000500,4,2147493648,213.1,\"peach\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536001000,5,2149783648,56.32,\"orange\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536010000,7,2147983648,213.112,\"lemon\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536100000,11,2147468648,54.121,\"pitaya\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536000000,12,2146483648,45.231,\"strawberry\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536000020,14,2907483648,231.34,\"cherry\",FALSE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536900000,13,2107483648,54.12,\"lychee\",TRUE)",
+ "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(51536000000,15,3147483648,235.213,\"watermelon\",TRUE)"
+ };
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvFactory.getEnv().initClusterEnvironment();
+ insertData();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ protected static void insertData() {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ for (String sql : sql) {
+ statement.execute(sql);
+ }
+ for (String sql : sql2) {
+ statement.execute(sql);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // ordinal data
+ String[][] res =
+ new String[][] {
+ {"0", "3", "2947483648", "231.2121", "coconut", "false"},
+ {"20", "2", "2147483648", "434.12", "pineapple", "true"},
+ {"40", "1", "2247483648", "12.123", "apricot", "true"},
+ {"80", "9", "2147483646", "43.12", "apple", "false"},
+ {"100", "8", "2147483964", "4654.231", "papaya", "true"},
+ {"31536000000", "6", "2147483650", "1231.21", "banana", "true"},
+ {"31536000100", "10", "3147483648", "231.55", "pumelo", "false"},
+ {"31536000500", "4", "2147493648", "213.1", "peach", "false"},
+ {"31536001000", "5", "2149783648", "56.32", "orange", "false"},
+ {"31536010000", "7", "2147983648", "213.112", "lemon", "true"},
+ {"31536100000", "11", "2147468648", "54.121", "pitaya", "false"},
+ {"41536000000", "12", "2146483648", "45.231", "strawberry", "false"},
+ {"41536000020", "14", "2907483648", "231.34", "cherry", "false"},
+ {"41536900000", "13", "2107483648", "54.12", "lychee", "true"},
+ {"51536000000", "15", "3147483648", "235.213", "watermelon", "true"},
+ };
+
+ private void checkHeader(ResultSetMetaData resultSetMetaData, String[] title)
+ throws SQLException {
+ for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+ assertEquals(title[i - 1], resultSetMetaData.getColumnName(i));
+ }
+ }
+
+ private void testNormalOrderBy(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData,
+ new String[] {
+ "Time",
+ "root.sg.d.num",
+ "root.sg.d.bigNum",
+ "root.sg.d.floatNum",
+ "root.sg.d.str",
+ "root.sg.d.bool"
+ });
+ int i = 0;
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ String actualNum = resultSet.getString(2);
+ String actualBigNum = resultSet.getString(3);
+ double actualFloatNum = resultSet.getDouble(4);
+ String actualStr = resultSet.getString(5);
+ String actualBool = resultSet.getString(6);
+
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(res[ans[i]][1], actualNum);
+ assertEquals(res[ans[i]][2], actualBigNum);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+ assertEquals(res[ans[i]][4], actualStr);
+ assertEquals(res[ans[i]][5], actualBool);
+
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ // 1. One-level order by test
+ @Test
+ public void orderByTest1() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by num";
+ int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest2() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum";
+ int[] ans = {13, 11, 10, 3, 1, 5, 4, 7, 9, 8, 2, 12, 0, 6, 14};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest3() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by floatNum";
+ int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest4() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest5() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by num desc";
+ int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+ testNormalOrderBy(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void orderByTest6() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum desc";
+ int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest7() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by floatNum desc";
+ int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+ testNormalOrderBy(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void orderByTest8() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderBy(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void orderByTest17() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc, str asc";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderBy(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void orderByTest18() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str, str";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest15() {
+ String sql = "select num+bigNum,floatNum from root.sg.d order by str";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData,
+ new String[] {"Time", "root.sg.d.num + root.sg.d.bigNum", "root.sg.d.floatNum"});
+ int i = 0;
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ double actualNum = resultSet.getDouble(2);
+ double actualFloat = resultSet.getDouble(3);
+
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(
+ Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.0001);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloat, 0.0001);
+
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ // 2. Multi-level order by test
+ @Test
+ public void orderByTest9() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bool asc, str asc";
+ int[] ans = {3, 12, 0, 8, 7, 10, 6, 11, 2, 5, 9, 13, 4, 1, 14};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest10() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bool asc, num desc";
+ int[] ans = {12, 11, 10, 6, 3, 8, 7, 0, 14, 13, 4, 9, 5, 1, 2};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest11() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum desc, floatNum desc";
+ int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest12() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc, floatNum desc";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderBy(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void orderByTest13() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.sg.d order by num+floatNum desc, floatNum desc";
+ int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+ testNormalOrderBy(sql, ans);
+ }
+
+ @Test
+ public void orderByTest14() {
+ String sql = "select num+bigNum from root.sg.d order by num+floatNum desc, floatNum desc";
+ int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(metaData, new String[] {"Time", "root.sg.d.num + root.sg.d.bigNum"});
+ int i = 0;
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ double actualNum = resultSet.getDouble(2);
+
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(
+ Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.001);
+
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByTest16() {
+ String sql = "select num+floatNum from root.sg.d order by floatNum+num desc, floatNum desc";
+ int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(metaData, new String[] {"Time", "root.sg.d.num + root.sg.d.floatNum"});
+ int i = 0;
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ double actualNum = resultSet.getDouble(2);
+
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(
+ Long.parseLong(res[ans[i]][1]) + Double.parseDouble(res[ans[i]][3]),
+ actualNum,
+ 0.001);
+
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ // 3. aggregation query
+ @Test
+ public void orderByInAggregationTest() {
+ String sql = "select avg(num) from root.sg.d group by session(10000ms) order by avg(num) desc";
+ double[][] ans = new double[][] {{15.0}, {13.0}, {13.0}, {11.0}, {6.4}, {4.6}};
+ long[] times =
+ new long[] {51536000000L, 41536000000L, 41536900000L, 31536100000L, 31536000000L, 0L};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[i], actualTime);
+ assertEquals(ans[i][0], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest2() {
+ String sql =
+ "select avg(num) from root.sg.d group by session(10000ms) order by max_value(floatNum)";
+ double[][] ans =
+ new double[][] {
+ {13.0, 54.12},
+ {11.0, 54.121},
+ {13.0, 231.34},
+ {15.0, 235.213},
+ {6.4, 1231.21},
+ {4.6, 4654.231}
+ };
+ long[] times =
+ new long[] {41536900000L, 31536100000L, 41536000000L, 51536000000L, 31536000000L, 0L};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[i], actualTime);
+ assertEquals(ans[i][0], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest3() {
+ String sql =
+ "select avg(num) from root.sg.d group by session(10000ms) order by avg(num) desc,max_value(floatNum)";
+ double[] ans = new double[] {15.0, 13.0, 13.0, 11.0, 6.4, 4.6};
+ long[] times =
+ new long[] {51536000000L, 41536900000L, 41536000000L, 31536100000L, 31536000000L, 0L};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[i], actualTime);
+ assertEquals(ans[i], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest4() {
+ String sql =
+ "select avg(num)+avg(floatNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+ double[][] ans =
+ new double[][] {{1079.56122}, {395.4584}, {65.121}, {151.2855}, {67.12}, {250.213}};
+ long[] times =
+ new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+ int[] order = new int[] {2, 4, 3, 5, 1, 0};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[order[i]], actualTime);
+ assertEquals(ans[order[i]][0], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest5() {
+ String sql =
+ "select min_value(bigNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+ long[] ans =
+ new long[] {2147483646L, 2147483650L, 2147468648L, 2146483648L, 2107483648L, 3147483648L};
+ long[] times =
+ new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+ int[] order = new int[] {2, 4, 3, 5, 1, 0};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ long actualMinValue = resultSet.getLong(2);
+ assertEquals(times[order[i]], actualTime);
+ assertEquals(ans[order[i]], actualMinValue, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest6() {
+ String sql =
+ "select min_value(num)+min_value(bigNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+ long[] ans =
+ new long[] {2147483647L, 2147483654L, 2147468659L, 2146483660L, 2107483661L, 3147483663L};
+ long[] times =
+ new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+ int[] order = new int[] {2, 4, 3, 5, 1, 0};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualMinValue = resultSet.getDouble(2);
+ assertEquals(times[order[i]], actualTime);
+ assertEquals(ans[order[i]], actualMinValue, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest7() {
+ String sql =
+ "select avg(num)+min_value(floatNum) from root.sg.d group by session(10000ms) order by max_value(floatNum)";
+ double[][] ans =
+ new double[][] {
+ {13.0, 54.12, 54.12},
+ {11.0, 54.121, 54.121},
+ {13.0, 231.34, 45.231},
+ {15.0, 235.213, 235.213},
+ {6.4, 1231.21, 56.32},
+ {4.6, 4654.231, 12.123}
+ };
+ long[] times =
+ new long[] {41536900000L, 31536100000L, 41536000000L, 51536000000L, 31536000000L, 0L};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[i], actualTime);
+ assertEquals(ans[i][0] + ans[i][2], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationTest8() {
+ String sql =
+ "select avg(num)+avg(floatNum) from root.sg.d group by session(10000ms) order by avg(floatNum)+avg(num)";
+ double[][] ans =
+ new double[][] {{1079.56122}, {395.4584}, {65.121}, {151.2855}, {67.12}, {250.213}};
+ long[] times =
+ new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+ int[] order = new int[] {2, 4, 3, 5, 1, 0};
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ double actualAvg = resultSet.getDouble(2);
+ assertEquals(times[order[i]], actualTime);
+ assertEquals(ans[order[i]][0], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ // 4. raw data query with align by device
+ private void testNormalOrderByAlignByDevice(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+ int i = 0;
+ int total = 0;
+ String device = "root.sg.d";
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ String actualDevice = resultSet.getString(2);
+ String actualNum = resultSet.getString(3);
+ String actualBigNum = resultSet.getString(4);
+ double actualFloatNum = resultSet.getDouble(5);
+ String actualStr = resultSet.getString(6);
+ String actualBool = resultSet.getString(7);
+
+ assertEquals(device, actualDevice);
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(res[ans[i]][1], actualNum);
+ assertEquals(res[ans[i]][2], actualBigNum);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+ assertEquals(res[ans[i]][4], actualStr);
+ assertEquals(res[ans[i]][5], actualBool);
+
+ if (device.equals("root.sg.d")) {
+ device = "root.sg.d2";
+ } else {
+ device = "root.sg.d";
+ i++;
+ }
+ total++;
+ }
+ assertEquals(i, ans.length);
+ assertEquals(total, ans.length * 2);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest1() {
+ String sql =
+ "select num+bigNum from root.** order by num+floatNum desc, floatNum desc align by device";
+ int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+ String device = "root.sg.d";
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ String actualTime = resultSet.getString(1);
+ String actualDevice = resultSet.getString(2);
+ double actualNum = resultSet.getDouble(3);
+
+ assertEquals(device, actualDevice);
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(
+ Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.0001);
+ if (device.equals("root.sg.d")) {
+ device = "root.sg.d2";
+ } else {
+ device = "root.sg.d";
+ i++;
+ }
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest2() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.** order by num align by device";
+ int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+ testNormalOrderByAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest3() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by floatNum align by device";
+ int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+ testNormalOrderByAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest4() {
+ String sql = "select num,bigNum,floatNum,str,bool from root.** order by str align by device";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderByAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest5() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by num desc align by device";
+ int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+ testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest6() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by str desc align by device";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest7() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by bool asc, num desc align by device";
+ int[] ans = {12, 11, 10, 6, 3, 8, 7, 0, 14, 13, 4, 9, 5, 1, 2};
+ testNormalOrderByAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest8() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, floatNum desc align by device";
+ int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ testNormalOrderByAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest9() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by str desc, floatNum desc align by device";
+ int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+ testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+ }
+
+ private void testNormalOrderByMixAlignBy(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+ int i = 0;
+ int total = 0;
+ String device = "root.sg.d";
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ String actualDevice = resultSet.getString(2);
+ String actualNum = resultSet.getString(3);
+ String actualBigNum = resultSet.getString(4);
+ double actualFloatNum = resultSet.getDouble(5);
+ String actualStr = resultSet.getString(6);
+ String actualBool = resultSet.getString(7);
+
+ assertEquals(device, actualDevice);
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(res[ans[i]][1], actualNum);
+ assertEquals(res[ans[i]][2], actualBigNum);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+ assertEquals(res[ans[i]][4], actualStr);
+ assertEquals(res[ans[i]][5], actualBool);
+
+ if (device.equals("root.sg.d2")) {
+ i++;
+ device = "root.sg.d";
+ } else {
+ device = "root.sg.d2";
+ }
+
+ total++;
+ }
+ assertEquals(total, ans.length * 2);
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ private void testDeviceViewOrderByMixAlignBy(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+ int i = 0;
+ int total = 0;
+ String device = "root.sg.d2";
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ String actualDevice = resultSet.getString(2);
+ String actualNum = resultSet.getString(3);
+ String actualBigNum = resultSet.getString(4);
+ double actualFloatNum = resultSet.getDouble(5);
+ String actualStr = resultSet.getString(6);
+ String actualBool = resultSet.getString(7);
+
+ assertEquals(device, actualDevice);
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(res[ans[i]][1], actualNum);
+ assertEquals(res[ans[i]][2], actualBigNum);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+ assertEquals(res[ans[i]][4], actualStr);
+ assertEquals(res[ans[i]][5], actualBool);
+
+ i++;
+ total++;
+ if (total == ans.length) {
+ i = 0;
+ if (device.equals("root.sg.d2")) {
+ device = "root.sg.d";
+ } else {
+ device = "root.sg.d2";
+ }
+ }
+ }
+ assertEquals(total, ans.length * 2);
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ private void orderByBigNumAlignByDevice(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ checkHeader(
+ metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+ int i = 0;
+ int total = 0;
+ String device = "root.sg.d";
+ while (resultSet.next()) {
+
+ String actualTime = resultSet.getString(1);
+ String actualDevice = resultSet.getString(2);
+ String actualNum = resultSet.getString(3);
+ String actualBigNum = resultSet.getString(4);
+ double actualFloatNum = resultSet.getDouble(5);
+ String actualStr = resultSet.getString(6);
+ String actualBool = resultSet.getString(7);
+
+ if (total < 4) {
+ i = total % 2;
+ if (total < 2) {
+ device = "root.sg.d2";
+ } else {
+ device = "root.sg.d";
+ }
+ }
+
+ assertEquals(device, actualDevice);
+ assertEquals(res[ans[i]][0], actualTime);
+ assertEquals(res[ans[i]][1], actualNum);
+ assertEquals(res[ans[i]][2], actualBigNum);
+ assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+ assertEquals(res[ans[i]][4], actualStr);
+ assertEquals(res[ans[i]][5], actualBool);
+
+ if (device.equals("root.sg.d2")) {
+ device = "root.sg.d";
+ } else {
+ i++;
+ device = "root.sg.d2";
+ }
+
+ total++;
+ }
+ assertEquals(i, ans.length);
+ assertEquals(total, ans.length * 2);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest12() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, device desc, time asc align by device";
+ int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ orderByBigNumAlignByDevice(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest13() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, time desc, device asc align by device";
+ int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ testNormalOrderByMixAlignBy(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest14() {
+ int[] ans = {14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by time desc, bigNum desc, device asc align by device";
+ testNormalOrderByMixAlignBy(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest15() {
+ int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by device desc, bigNum desc, time asc align by device";
+ testDeviceViewOrderByMixAlignBy(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest16() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by device desc, time asc, bigNum desc align by device";
+ int[] ans = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ testDeviceViewOrderByMixAlignBy(sql, ans);
+ }
+
+ @Test
+ public void alignByDeviceOrderByTest17() {
+ String sql =
+ "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, device desc, num asc, time asc align by device";
+ int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+ orderByBigNumAlignByDevice(sql, ans);
+ }
+
+ // 5. aggregation query align by device
+ @Test
+ public void orderByInAggregationAlignByDeviceTest() {
+ String sql =
+ "select avg(num) from root.** group by session(10000ms) order by avg(num) align by device";
+
+ double[] ans = {4.6, 4.6, 6.4, 6.4, 11.0, 11.0, 13.0, 13.0, 13.0, 13.0, 15.0, 15.0};
+ long[] times =
+ new long[] {
+ 0L,
+ 0L,
+ 31536000000L,
+ 31536000000L,
+ 31536100000L,
+ 31536100000L,
+ 41536000000L,
+ 41536900000L,
+ 41536000000L,
+ 41536900000L,
+ 51536000000L,
+ 51536000000L
+ };
+ String[] device =
+ new String[] {
+ "root.sg.d",
+ "root.sg.d2",
+ "root.sg.d",
+ "root.sg.d2",
+ "root.sg.d",
+ "root.sg.d2",
+ "root.sg.d",
+ "root.sg.d",
+ "root.sg.d2",
+ "root.sg.d2",
+ "root.sg.d",
+ "root.sg.d2"
+ };
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ long actualTime = resultSet.getLong(1);
+ String actualDevice = resultSet.getString(2);
+ double actualAvg = resultSet.getDouble(3);
+
+ assertEquals(device[i], actualDevice);
+ assertEquals(times[i], actualTime);
+ assertEquals(ans[i], actualAvg, 0.0001);
+ i++;
+ }
+ assertEquals(i, ans.length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest2() {
+ String sql = "select avg(num) from root.** order by avg(num) align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ private void checkSingleDouble(String sql, Object value, boolean deviceAsc) {
+ String device = "root.sg.d";
+ if (!deviceAsc) device = "root.sg.d2";
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ String deviceName = resultSet.getString(1);
+ double actualVal = resultSet.getDouble(2);
+ assertEquals(deviceName, device);
+ assertEquals(Double.parseDouble(value.toString()), actualVal, 1);
+ if (device.equals("root.sg.d")) device = "root.sg.d2";
+ else device = "root.sg.d";
+ i++;
+ }
+ assertEquals(i, 2);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest3() {
+ String sql =
+ "select avg(num)+avg(bigNum) from root.** order by max_value(floatNum) align by device";
+ long value = 2388936669L + 8;
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest4() {
+
+ String sql =
+ "select avg(num)+avg(bigNum) from root.** order by max_value(floatNum)+min_value(num) align by device";
+ long value = 2388936669L + 8;
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest5() {
+ String sql =
+ "select avg(num) from root.** order by max_value(floatNum)+avg(num) align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest6() {
+ String sql =
+ "select avg(num) from root.** order by max_value(floatNum)+avg(num), device asc, time desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest7() {
+ String sql =
+ "select avg(num) from root.** order by max_value(floatNum)+avg(num), time asc, device desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, false);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest8() {
+ String sql =
+ "select avg(num) from root.** order by time asc, max_value(floatNum)+avg(num), device desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, false);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest9() {
+ String sql =
+ "select avg(num) from root.** order by device asc, max_value(floatNum)+avg(num), time desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest10() {
+ String sql =
+ "select avg(num) from root.** order by max_value(floatNum) desc,time asc, avg(num) asc, device desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, false);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest11() {
+ String sql =
+ "select avg(num) from root.** order by max_value(floatNum) desc,device asc, avg(num) asc, time desc align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest12() {
+ String sql =
+ "select avg(num+floatNum) from root.** order by time,avg(num+floatNum) align by device";
+ String value = "537.34154";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest13() {
+ String sql = "select avg(num) from root.** order by time,avg(num+floatNum) align by device";
+ String value = "8";
+ checkSingleDouble(sql, value, true);
+ }
+
+ @Test
+ public void orderByInAggregationAlignByDeviceTest14() {
+ String sql = "select avg(num+floatNum) from root.** order by time,avg(num) align by device";
+ String value = "537.34154";
+ checkSingleDouble(sql, value, true);
+ }
+
+ String[][] UDFRes =
+ new String[][] {
+ {"0", "3", "0", "0"},
+ {"20", "2", "0", "0"},
+ {"40", "1", "0", "0"},
+ {"80", "9", "0", "0"},
+ {"100", "8", "0", "0"},
+ {"31536000000", "6", "0", "0"},
+ {"31536000100", "10", "0", "0"},
+ {"31536000500", "4", "0", "0"},
+ {"31536001000", "5", "0", "0"},
+ {"31536010000", "7", "0", "0"},
+ {"31536100000", "11", "0", "0"},
+ {"41536000000", "12", "2146483648", "0"},
+ {"41536000020", "14", "0", "14"},
+ {"41536900000", "13", "2107483648", "0"},
+ {"51536000000", "15", "0", "15"},
+ };
+
+ // UDF Test
+ private void orderByUDFTest(String sql, int[] ans) {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ int i = 0;
+ while (resultSet.next()) {
+ String time = resultSet.getString(1);
+ String num = resultSet.getString(2);
+ String topK = resultSet.getString(3);
+ String bottomK = resultSet.getString(4);
+
+ assertEquals(time, UDFRes[ans[i]][0]);
+ assertEquals(num, UDFRes[ans[i]][1]);
+ if (Objects.equals(UDFRes[ans[i]][3], "0")) {
+ assertNull(topK);
+ } else {
+ assertEquals(topK, UDFRes[ans[i]][3]);
+ }
+
+ if (Objects.equals(UDFRes[ans[i]][2], "0")) {
+ assertNull(bottomK);
+ } else {
+ assertEquals(bottomK, UDFRes[ans[i]][2]);
+ }
+
+ i++;
+ }
+ assertEquals(i, 15);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void orderByUDFTest1() {
+ String sql =
+ "select num, top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2') from root.sg.d order by top_k(num, 'k'='2') nulls first, bottom_k(bigNum, 'k'='2') nulls first";
+ int[] ans = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 11, 12, 14};
+ orderByUDFTest(sql, ans);
+ }
+
+ @Test
+ public void orderByUDFTest2() {
+ String sql =
+ "select num, top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2') from root.sg.d order by top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2')";
+ int[] ans = {12, 14, 13, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ orderByUDFTest(sql, ans);
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
index ae61601063..6e9d185251 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
@@ -65,6 +65,16 @@ public class SortOperator implements ProcessOperator {
@Override
public TsBlock next() throws Exception {
+
+ if (!inputOperator.hasNextWithTimer()) {
+ if (cachedData.size() > 1) {
+ cachedData.sort(comparator);
+ }
+ TsBlock result = buildTsBlock();
+ cachedData = null;
+ return result;
+ }
+
TsBlock tsBlock = inputOperator.nextWithTimer();
if (tsBlock == null) {
return null;
@@ -73,17 +83,7 @@ public class SortOperator implements ProcessOperator {
for (int i = 0; i < tsBlock.getPositionCount(); i++) {
cachedData.add(new MergeSortKey(tsBlock, i));
}
- // child has more data, can't calculate
- if (inputOperator.hasNextWithTimer()) {
- return null;
- }
-
- if (cachedData.size() > 1) {
- cachedData.sort(comparator);
- }
- TsBlock result = buildTsBlock();
- cachedData = null;
- return result;
+ return null;
}
private TsBlock buildTsBlock() {
@@ -95,6 +95,10 @@ public class SortOperator implements ProcessOperator {
int row = mergeSortKey.rowIndex;
timeColumnBuilder.writeLong(tsBlock.getTimeByIndex(row));
for (int i = 0; i < valueColumnBuilders.length; i++) {
+ if (tsBlock.getColumn(i).isNull(row)) {
+ valueColumnBuilders[i].appendNull();
+ continue;
+ }
valueColumnBuilders[i].write(tsBlock.getColumn(i), row);
}
tsBlockBuilder.declarePosition();
@@ -104,7 +108,7 @@ public class SortOperator implements ProcessOperator {
@Override
public boolean hasNext() throws Exception {
- return inputOperator.hasNextWithTimer();
+ return inputOperator.hasNextWithTimer() || cachedData != null;
}
@Override
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
index 16b66b8dd3..3d8991bf24 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
@@ -19,9 +19,9 @@
package org.apache.iotdb.db.mpp.execution.operator.process.join.merge;
+import org.apache.iotdb.db.mpp.plan.statement.component.NullOrdering;
import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
-import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
import org.apache.iotdb.db.utils.datastructure.MergeSortKey;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -33,153 +33,33 @@ import java.util.List;
public class MergeSortComparator {
- public static final Comparator<MergeSortKey> ASC_TIME_ASC_DEVICE =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int timeComparing =
- Long.compare(
- o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex));
- return timeComparing == 0
- ? o1.tsBlock
- .getColumn(0)
- .getBinary(o1.rowIndex)
- .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex))
- : timeComparing;
- };
- public static final Comparator<MergeSortKey> ASC_TIME_DESC_DEVICE =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int timeComparing =
- Long.compare(
- o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex));
- return timeComparing == 0
- ? o2.tsBlock
- .getColumn(0)
- .getBinary(o2.rowIndex)
- .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex))
- : timeComparing;
- };
- public static final Comparator<MergeSortKey> DESC_TIME_ASC_DEVICE =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int timeComparing =
- Long.compare(
- o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex));
- return timeComparing == 0
- ? o1.tsBlock
- .getColumn(0)
- .getBinary(o1.rowIndex)
- .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex))
- : timeComparing;
- };
- public static final Comparator<MergeSortKey> DESC_TIME_DESC_DEVICE =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int timeComparing =
- Long.compare(
- o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex));
- return timeComparing == 0
- ? o2.tsBlock
- .getColumn(0)
- .getBinary(o2.rowIndex)
- .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex))
- : timeComparing;
- };
+ private static final Comparator<MergeSortKey> TIME_ASC_COMPARATOR =
+ Comparator.comparingLong(
+ (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex));
- public static final Comparator<MergeSortKey> ASC_DEVICE_ASC_TIME =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int deviceComparing =
- o1.tsBlock
- .getColumn(0)
- .getBinary(o1.rowIndex)
- .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex));
- return deviceComparing == 0
- ? Long.compare(
- o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex))
- : deviceComparing;
- };
- public static final Comparator<MergeSortKey> ASC_DEVICE_DESC_TIME =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int deviceComparing =
- o1.tsBlock
- .getColumn(0)
- .getBinary(o1.rowIndex)
- .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex));
- return deviceComparing == 0
- ? Long.compare(
- o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex))
- : deviceComparing;
- };
- public static final Comparator<MergeSortKey> DESC_DEVICE_ASC_TIME =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int deviceComparing =
- o2.tsBlock
- .getColumn(0)
- .getBinary(o2.rowIndex)
- .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex));
- return deviceComparing == 0
- ? Long.compare(
- o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex))
- : deviceComparing;
- };
- public static final Comparator<MergeSortKey> DESC_DEVICE_DESC_TIME =
- (MergeSortKey o1, MergeSortKey o2) -> {
- int deviceComparing =
- o2.tsBlock
- .getColumn(0)
- .getBinary(o2.rowIndex)
- .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex));
- return deviceComparing == 0
- ? Long.compare(
- o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex))
- : deviceComparing;
- };
+ private static final Comparator<MergeSortKey> TIME_DESC_COMPARATOR =
+ Comparator.comparingLong(
+ (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex))
+ .reversed();
+ /** @param indexList -1 for time column */
public static Comparator<MergeSortKey> getComparator(
List<SortItem> sortItemList, List<Integer> indexList, List<TSDataType> dataTypeList) {
- // specified for order by time, device or order by device, time
- if (sortItemList.size() == 2
- && ((sortItemList.get(0).getSortKey() == SortKey.TIME
- && sortItemList.get(1).getSortKey() == SortKey.DEVICE)
- || (sortItemList.get(0).getSortKey() == SortKey.DEVICE
- && sortItemList.get(1).getSortKey() == SortKey.TIME))) {
- if (sortItemList.get(0).getOrdering() == Ordering.ASC) {
- if (sortItemList.get(1).getOrdering() == Ordering.ASC) {
- if (sortItemList.get(0).getSortKey() == SortKey.TIME) return ASC_TIME_ASC_DEVICE;
- else return ASC_DEVICE_ASC_TIME;
- } else {
- if (sortItemList.get(0).getSortKey() == SortKey.TIME) return ASC_TIME_DESC_DEVICE;
- else return ASC_DEVICE_DESC_TIME;
- }
- } else {
- if (sortItemList.get(1).getOrdering() == Ordering.ASC) {
- if (sortItemList.get(0).getSortKey() == SortKey.TIME) return DESC_TIME_ASC_DEVICE;
- else return DESC_DEVICE_ASC_TIME;
- } else {
- if (sortItemList.get(0).getSortKey() == SortKey.TIME) return DESC_TIME_DESC_DEVICE;
- else return DESC_DEVICE_DESC_TIME;
- }
- }
- } else { // generally order by
- // use code-gen compile this comparator
- // currently only show queries use merge sort and will only contain TIME, QUERYID, DATANODEID,
- // ELAPSEDTIME, STATEMENT
- return genComparatorChain(sortItemList, indexList, dataTypeList);
- }
- }
-
- /** @param indexList -1 for time column */
- private static ComparatorChain<MergeSortKey> genComparatorChain(
- List<SortItem> sortItemList, List<Integer> indexList, List<TSDataType> dataTypeList) {
+ // use code-gen compile this comparator
List<Comparator<MergeSortKey>> list = new ArrayList<>(indexList.size());
for (int i = 0; i < indexList.size(); i++) {
int index = indexList.get(i);
TSDataType dataType = dataTypeList.get(i);
boolean asc = sortItemList.get(i).getOrdering() == Ordering.ASC;
- list.add(genSingleComparator(asc, index, dataType));
+ boolean nullFirst = sortItemList.get(i).getNullOrdering() == NullOrdering.FIRST;
+ list.add(genSingleComparator(asc, index, dataType, nullFirst));
}
return new ComparatorChain<>(list);
}
- private static Comparator<MergeSortKey> genSingleComparator(
- boolean asc, int index, TSDataType dataType) {
+ public static Comparator<MergeSortKey> getComparator(
+ TSDataType dataType, int index, boolean asc) {
Comparator<MergeSortKey> comparator;
switch (dataType) {
case INT32:
@@ -189,16 +69,10 @@ public class MergeSortComparator {
sortKey.tsBlock.getColumn(index).getInt(sortKey.rowIndex));
break;
case INT64:
- if (index == -1) {
- comparator =
- Comparator.comparingLong(
- (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex));
- } else {
- comparator =
- Comparator.comparingLong(
- (MergeSortKey sortKey) ->
- sortKey.tsBlock.getColumn(index).getLong(sortKey.rowIndex));
- }
+ comparator =
+ Comparator.comparingLong(
+ (MergeSortKey sortKey) ->
+ sortKey.tsBlock.getColumn(index).getLong(sortKey.rowIndex));
break;
case FLOAT:
comparator =
@@ -218,12 +92,28 @@ public class MergeSortComparator {
(MergeSortKey sortKey) ->
sortKey.tsBlock.getColumn(index).getBinary(sortKey.rowIndex));
break;
+ case BOOLEAN:
+ comparator =
+ Comparator.comparing(
+ (MergeSortKey sortKey) ->
+ sortKey.tsBlock.getColumn(index).getBoolean(sortKey.rowIndex));
+ break;
default:
throw new IllegalArgumentException("Data type: " + dataType + " cannot be ordered");
}
if (!asc) {
comparator = comparator.reversed();
}
+
return comparator;
}
+
+ private static Comparator<MergeSortKey> genSingleComparator(
+ boolean asc, int index, TSDataType dataType, boolean nullFirst) {
+
+ if (index == -1) {
+ return asc ? TIME_ASC_COMPARATOR : TIME_DESC_COMPARATOR;
+ }
+ return new MergeSortKeyComparator(index, nullFirst, getComparator(dataType, index, asc));
+ }
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java
new file mode 100644
index 0000000000..8e8e8c6f85
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.execution.operator.process.join.merge;
+
+import org.apache.iotdb.db.utils.datastructure.MergeSortKey;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+public class MergeSortKeyComparator implements Comparator<MergeSortKey>, Serializable {
+
+ private final boolean nullFirst;
+ private final int index;
+ private final Comparator<MergeSortKey> originalComparator;
+
+ public MergeSortKeyComparator(
+ int index, boolean nullFirst, Comparator<MergeSortKey> originalComparator) {
+ this.nullFirst = nullFirst;
+ this.index = index;
+ this.originalComparator = originalComparator;
+ }
+
+ @Override
+ public int compare(MergeSortKey o1, MergeSortKey o2) {
+ boolean o1IsNull = o1.tsBlock.getColumn(index).isNull(o1.rowIndex);
+ boolean o2IsNull = o2.tsBlock.getColumn(index).isNull(o2.rowIndex);
+
+ if (!o1IsNull && !o2IsNull) {
+ return originalComparator.compare(o1, o2);
+ } else if (o1IsNull) {
+ return o2IsNull ? 0 : (nullFirst ? -1 : 1);
+ } else {
+ return nullFirst ? 1 : -1;
+ }
+ }
+}
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 3c4fd6e9ed..6b3f4780a6 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
@@ -39,6 +39,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.GroupByTimeParameter;
import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.IntoPathDescriptor;
import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.OrderByParameter;
import org.apache.iotdb.db.mpp.plan.statement.Statement;
+import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
import org.apache.iotdb.db.mpp.plan.statement.sys.ShowQueriesStatement;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -141,6 +142,12 @@ public class Analysis {
// expression of group by that need to be calculated
private Map<String, Expression> deviceToGroupByExpression;
+ // expression of order by that need to be calculated
+ private Map<String, Set<Expression>> deviceToOrderByExpressions;
+
+ // the sortItems used in order by push down of align by device
+ private Map<String, List<SortItem>> deviceToSortItems;
+
// 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>> deviceViewInputIndexesMap;
@@ -166,6 +173,15 @@ public class Analysis {
private Expression havingExpression;
+ // The expressions in order by clause
+ // In align by device orderByExpression is the deviceView of expression which doesn't have
+ // device-prefix
+ // for example, in device root.sg1.d1, [root.sg1.d1.s1] is expression and [s1] is the device-view
+ // one.
+ private Set<Expression> orderByExpressions;
+
+ private boolean orderByExpressionInDeviceView = false;
+
// parameter of `FILL` clause
private FillDescriptor fillDescriptor;
@@ -617,4 +633,37 @@ public class Analysis {
public Map<NodeRef<Expression>, TSDataType> getExpressionTypes() {
return expressionTypes;
}
+
+ public void setOrderByExpressions(Set<Expression> orderByExpressions) {
+ this.orderByExpressions = orderByExpressions;
+ }
+
+ public Set<Expression> getOrderByExpressions() {
+ return orderByExpressions;
+ }
+
+ public Map<String, Set<Expression>> getDeviceToOrderByExpressions() {
+ return deviceToOrderByExpressions;
+ }
+
+ public void setDeviceToOrderByExpressions(
+ Map<String, Set<Expression>> deviceToOrderByExpressions) {
+ this.deviceToOrderByExpressions = deviceToOrderByExpressions;
+ }
+
+ public void setOrderByExpressionInDeviceView(boolean orderByExpressionInDeviceView) {
+ this.orderByExpressionInDeviceView = orderByExpressionInDeviceView;
+ }
+
+ public boolean isOrderByExpressionInDeviceView() {
+ return orderByExpressionInDeviceView;
+ }
+
+ public Map<String, List<SortItem>> getDeviceToSortItems() {
+ return deviceToSortItems;
+ }
+
+ public void setDeviceToSortItems(Map<String, List<SortItem>> deviceToSortItems) {
+ this.deviceToSortItems = deviceToSortItems;
+ }
}
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 52ad29445d..30162d3ad0 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
@@ -289,6 +289,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
analyzeDeviceToGroupBy(analysis, queryStatement, schemaTree, deviceSet);
Map<String, Set<Expression>> deviceToAggregationExpressions = new HashMap<>();
+ analyzeDeviceToOrderBy(analysis, queryStatement, schemaTree, deviceSet);
analyzeHaving(
analysis, queryStatement, schemaTree, deviceSet, deviceToAggregationExpressions);
analyzeDeviceToAggregation(analysis, queryStatement, deviceToAggregationExpressions);
@@ -313,6 +314,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
analyzeGroupBy(analysis, queryStatement, schemaTree);
analyzeHaving(analysis, queryStatement, schemaTree);
+ analyzeOrderBy(analysis, queryStatement, schemaTree);
analyzeGroupByLevel(analysis, queryStatement, outputExpressionMap, outputExpressions);
analyzeGroupByTag(analysis, queryStatement, outputExpressions);
@@ -413,7 +415,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
if (orderByParameter != null && !orderByParameter.getSortItemList().isEmpty()) {
List<SortItem> sortItemList = orderByParameter.getSortItemList();
checkState(
- sortItemList.size() == 1 && sortItemList.get(0).getSortKey() == SortKey.TIMESERIES,
+ sortItemList.size() == 1 && sortItemList.get(0).getSortKey().equals(SortKey.TIMESERIES),
"Last queries only support sorting by timeseries now.");
boolean isAscending = sortItemList.get(0).getOrdering() == Ordering.ASC;
sourceExpressions =
@@ -880,6 +882,18 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
.computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
.addAll(aggregationExpressions);
}
+
+ if (queryStatement.hasOrderByExpression()) {
+ Map<String, Set<Expression>> deviceToOrderByExpressions =
+ analysis.getDeviceToOrderByExpressions();
+ for (String deviceName : deviceToOrderByExpressions.keySet()) {
+ for (Expression orderByExpression : deviceToOrderByExpressions.get(deviceName)) {
+ deviceToAggregationExpressions
+ .computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
+ .addAll(ExpressionAnalyzer.searchAggregationExpressions(orderByExpression));
+ }
+ }
+ }
}
private void analyzeAggregation(Analysis analysis, QueryStatement queryStatement) {
@@ -902,6 +916,12 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
aggregationExpressions.addAll(
ExpressionAnalyzer.searchAggregationExpressions(analysis.getHavingExpression()));
}
+ if (queryStatement.hasOrderByExpression()) {
+ for (Expression expression : analysis.getOrderByExpressions()) {
+ aggregationExpressions.addAll(
+ ExpressionAnalyzer.searchAggregationExpressions(expression));
+ }
+ }
analysis.setAggregationExpressions(aggregationExpressions);
}
}
@@ -928,6 +948,15 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
}
} else {
deviceToSourceTransformExpressions = analysis.getDeviceToSelectExpressions();
+ if (queryStatement.hasOrderByExpression()) {
+ Map<String, Set<Expression>> deviceToOrderByExpressions =
+ analysis.getDeviceToOrderByExpressions();
+ for (String deviceName : deviceToOrderByExpressions.keySet()) {
+ for (Expression expression : deviceToOrderByExpressions.get(deviceName)) {
+ deviceToSourceTransformExpressions.get(deviceName).add(expression);
+ }
+ }
+ }
}
analysis.setDeviceToSourceTransformExpressions(deviceToSourceTransformExpressions);
}
@@ -939,11 +968,14 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
// for AggregationExpression, only the first Expression of input need to transform
sourceTransformExpressions.add(expression.getExpressions().get(0));
}
+ if (queryStatement.hasGroupByExpression()) {
+ sourceTransformExpressions.add(analysis.getGroupByExpression());
+ }
} else {
- sourceTransformExpressions = analysis.getSelectExpressions();
- }
- if (queryStatement.hasGroupByExpression()) {
- sourceTransformExpressions.add(analysis.getGroupByExpression());
+ sourceTransformExpressions.addAll(analysis.getSelectExpressions());
+ if (queryStatement.hasOrderByExpression()) {
+ sourceTransformExpressions.addAll(analysis.getOrderByExpressions());
+ }
}
analysis.setSourceTransformExpressions(sourceTransformExpressions);
}
@@ -1090,8 +1122,17 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
deviceViewOutputExpressions.addAll(
ExpressionAnalyzer.searchAggregationExpressions(analysis.getHavingExpression()));
}
+ if (queryStatement.hasOrderByExpression()) {
+ for (Expression orderByExpression : analysis.getOrderByExpressions()) {
+ deviceViewOutputExpressions.addAll(
+ ExpressionAnalyzer.searchAggregationExpressions(orderByExpression));
+ }
+ }
} else {
- deviceViewOutputExpressions = selectExpressions;
+ deviceViewOutputExpressions.addAll(selectExpressions);
+ if (queryStatement.hasOrderByExpression()) {
+ deviceViewOutputExpressions.addAll(analysis.getOrderByExpressions());
+ }
}
analysis.setDeviceViewOutputExpressions(deviceViewOutputExpressions);
@@ -1180,10 +1221,35 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
analysis.setRespDatasetHeader(new DatasetHeader(columnHeaders, isIgnoreTimestamp));
}
+ // For last query
private void analyzeOrderBy(Analysis analysis, QueryStatement queryStatement) {
analysis.setMergeOrderParameter(new OrderByParameter(queryStatement.getSortItemList()));
}
+ private void analyzeOrderBy(
+ Analysis analysis, QueryStatement queryStatement, ISchemaTree schemaTree) {
+ if (!queryStatement.hasOrderByExpression()) return;
+
+ Set<Expression> orderByExpressions = new LinkedHashSet<>();
+ for (Expression expressionForItem : queryStatement.getExpressionSortItemList()) {
+ // Expression in a sortItem only indicates one column
+ List<Expression> expressions =
+ ExpressionAnalyzer.removeWildcardInExpression(expressionForItem, schemaTree);
+ if (expressions.size() != 1) {
+ throw new SemanticException("One sort item in order by should only indicate one value");
+ }
+ expressionForItem = expressions.get(0);
+ TSDataType dataType = analyzeExpression(analysis, expressionForItem);
+ if (!dataType.isComparable()) {
+ throw new SemanticException(
+ String.format("The data type of sort item %s is not comparable", dataType));
+ }
+ orderByExpressions.add(expressionForItem);
+ }
+ analysis.setOrderByExpressions(orderByExpressions);
+ queryStatement.updateSortItems(orderByExpressions);
+ }
+
private TSDataType analyzeExpression(Analysis analysis, Expression expression) {
ExpressionTypeAnalyzer.analyzeExpression(analysis, expression);
return analysis.getType(expression);
@@ -1258,6 +1324,56 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
}
}
+ private void analyzeDeviceToOrderBy(
+ Analysis analysis,
+ QueryStatement queryStatement,
+ ISchemaTree schemaTree,
+ Set<PartialPath> deviceSet) {
+
+ if (!queryStatement.hasOrderByExpression()) {
+ return;
+ }
+
+ Map<String, Set<Expression>> deviceToOrderByExpressions = new LinkedHashMap<>();
+ Map<String, List<SortItem>> deviceToSortItems = new LinkedHashMap<>();
+ // build the device-view outputColumn for the sortNode above the deviceViewNode
+ Set<Expression> deviceViewOrderByExpression = new LinkedHashSet<>();
+ for (PartialPath device : deviceSet) {
+ Set<Expression> orderByExpressionsForOneDevice = new LinkedHashSet<>();
+ for (Expression expressionForItem : queryStatement.getExpressionSortItemList()) {
+ List<Expression> expressions =
+ ExpressionAnalyzer.concatDeviceAndRemoveWildcard(expressionForItem, device, schemaTree);
+ if (expressions.size() != 1) {
+ throw new SemanticException(
+ String.format(
+ "One sort item in order by should only indicate one value, got %s value(s)",
+ expressions.size()));
+ }
+ expressionForItem = expressions.get(0);
+ TSDataType dataType = analyzeExpression(analysis, expressionForItem);
+ if (!dataType.isComparable()) {
+ throw new SemanticException(
+ String.format("The data type of sort item %s is not comparable", dataType));
+ }
+
+ Expression devicerViewExpression =
+ ExpressionAnalyzer.getMeasurementExpression(expressionForItem);
+ analyzeExpression(analysis, devicerViewExpression);
+
+ deviceViewOrderByExpression.add(devicerViewExpression);
+ orderByExpressionsForOneDevice.add(expressionForItem);
+ }
+ deviceToSortItems.put(
+ device.getFullPath(), queryStatement.getUpdatedSortItems(orderByExpressionsForOneDevice));
+ deviceToOrderByExpressions.put(device.getFullPath(), orderByExpressionsForOneDevice);
+ }
+
+ analysis.setOrderByExpressions(deviceViewOrderByExpression);
+ queryStatement.updateSortItems(deviceViewOrderByExpression);
+ analysis.setDeviceToSortItems(deviceToSortItems);
+ analysis.setDeviceToOrderByExpressions(deviceToOrderByExpressions);
+ }
+
private void analyzeGroupBy(
Analysis analysis, QueryStatement queryStatement, ISchemaTree schemaTree) {
@@ -2589,9 +2705,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
schemaTree
.getMatchedDevices(new PartialPath(devicePattern))
.forEach(
- deviceSchemaInfo -> {
- deduplicatedDevicePaths.add(deviceSchemaInfo.getDevicePath().getFullPath());
- });
+ deviceSchemaInfo ->
+ deduplicatedDevicePaths.add(deviceSchemaInfo.getDevicePath().getFullPath()));
} catch (IllegalPathException ignored) {
// won't happen
}
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 1dca937376..c1a611cafc 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
@@ -61,6 +61,12 @@ public class ConcatPathRewriter {
prefixPaths,
patternTree);
}
+ if (queryStatement.hasOrderByExpression()) {
+ for (Expression sortItemExpression : queryStatement.getExpressionSortItemList()) {
+ ExpressionAnalyzer.constructPatternTreeFromExpression(
+ sortItemExpression, prefixPaths, patternTree);
+ }
+ }
} else {
// concat SELECT with FROM
List<ResultColumn> resultColumns =
@@ -68,7 +74,7 @@ public class ConcatPathRewriter {
queryStatement.getSelectComponent(), prefixPaths, queryStatement.isGroupByLevel());
queryStatement.getSelectComponent().setResultColumns(resultColumns);
- // concat GROUP BY VARIATION with FROM
+ // concat GROUP BY with FROM
if (queryStatement.hasGroupByExpression()) {
queryStatement
.getGroupByComponent()
@@ -77,6 +83,11 @@ public class ConcatPathRewriter {
queryStatement.getGroupByComponent().getControlColumnExpression(),
prefixPaths));
}
+ if (queryStatement.hasOrderByExpression()) {
+ List<Expression> sortItemExpressions = queryStatement.getExpressionSortItemList();
+ sortItemExpressions.replaceAll(
+ expression -> contactOrderByWithFrom(expression, prefixPaths));
+ }
}
// concat WHERE with FROM
@@ -124,4 +135,13 @@ public class ConcatPathRewriter {
}
return resultExpressions.get(0);
}
+
+ private Expression contactOrderByWithFrom(Expression expression, List<PartialPath> prefixPaths) {
+ List<Expression> resultExpressions =
+ ExpressionAnalyzer.concatExpressionWithSuffixPaths(expression, prefixPaths, patternTree);
+ if (resultExpressions.size() != 1) {
+ throw new IllegalStateException("Expression in order by should indicate one value");
+ }
+ return resultExpressions.get(0);
+ }
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
index 90cc519cbe..2a11d8e896 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
@@ -94,7 +94,8 @@ public class ColumnTransformerVisitor
@Override
public ColumnTransformer visitUnaryExpression(
UnaryExpression unaryExpression, ColumnTransformerVisitorContext context) {
- if (!context.cache.containsKey(unaryExpression)) {
+ if (!context.cache.containsKey(unaryExpression)
+ && !generateIdentityColumnTransformerIfPossible(unaryExpression, context)) {
if (context.hasSeen.containsKey(unaryExpression)) {
IdentityColumnTransformer identity =
new IdentityColumnTransformer(
@@ -125,7 +126,8 @@ public class ColumnTransformerVisitor
@Override
public ColumnTransformer visitBinaryExpression(
BinaryExpression binaryExpression, ColumnTransformerVisitorContext context) {
- if (!context.cache.containsKey(binaryExpression)) {
+ if (!context.cache.containsKey(binaryExpression)
+ && !generateIdentityColumnTransformerIfPossible(binaryExpression, context)) {
if (context.hasSeen.containsKey(binaryExpression)) {
IdentityColumnTransformer identity =
new IdentityColumnTransformer(
@@ -160,7 +162,8 @@ public class ColumnTransformerVisitor
@Override
public ColumnTransformer visitTernaryExpression(
TernaryExpression ternaryExpression, ColumnTransformerVisitorContext context) {
- if (!context.cache.containsKey(ternaryExpression)) {
+ if (!context.cache.containsKey(ternaryExpression)
+ && !generateIdentityColumnTransformerIfPossible(ternaryExpression, context)) {
if (context.hasSeen.containsKey(ternaryExpression)) {
IdentityColumnTransformer identity =
new IdentityColumnTransformer(
@@ -199,7 +202,8 @@ public class ColumnTransformerVisitor
public ColumnTransformer visitFunctionExpression(
FunctionExpression functionExpression, ColumnTransformerVisitorContext context) {
List<Expression> expressions = functionExpression.getExpressions();
- if (!context.cache.containsKey(functionExpression)) {
+ if (!context.cache.containsKey(functionExpression)
+ && !generateIdentityColumnTransformerIfPossible(functionExpression, context)) {
if (context.hasSeen.containsKey(functionExpression)) {
IdentityColumnTransformer identity =
new IdentityColumnTransformer(
@@ -344,7 +348,8 @@ public class ColumnTransformerVisitor
@Override
public ColumnTransformer visitCaseWhenThenExpression(
CaseWhenThenExpression caseWhenThenExpression, ColumnTransformerVisitorContext context) {
- if (!context.cache.containsKey(caseWhenThenExpression)) {
+ if (!context.cache.containsKey(caseWhenThenExpression)
+ && !generateIdentityColumnTransformerIfPossible(caseWhenThenExpression, context)) {
if (context.hasSeen.containsKey(caseWhenThenExpression)) {
IdentityColumnTransformer identity =
new IdentityColumnTransformer(
@@ -381,6 +386,29 @@ public class ColumnTransformerVisitor
return res;
}
+ /**
+ * the input could be calculated expressions that we can use directly and we do not need to do
+ * further calculation if so
+ *
+ * @return true if the expression has been calculated
+ */
+ private boolean generateIdentityColumnTransformerIfPossible(
+ Expression expression, ColumnTransformerVisitorContext context) {
+ List<InputLocation> inputLocations =
+ context.inputLocations.get(expression.getExpressionString());
+ if (inputLocations != null) {
+ IdentityColumnTransformer identity =
+ new IdentityColumnTransformer(
+ TypeFactory.getType(context.getType(expression)),
+ inputLocations.get(0).getValueColumnIndex());
+ // add to leafList
+ context.leafList.add(identity);
+ context.cache.put(expression, identity);
+ return true;
+ }
+ return false;
+ }
+
private ColumnTransformer getConcreteUnaryColumnTransformer(
Expression expression, ColumnTransformer childColumnTransformer, Type returnType) {
switch (expression.getExpressionType()) {
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 9475f5ca5b..4e9986d4fb 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
@@ -85,6 +85,7 @@ import org.apache.iotdb.db.mpp.plan.statement.component.GroupByVariationComponen
import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
import org.apache.iotdb.db.mpp.plan.statement.component.IntoComponent;
import org.apache.iotdb.db.mpp.plan.statement.component.IntoItem;
+import org.apache.iotdb.db.mpp.plan.statement.component.NullOrdering;
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;
@@ -1372,32 +1373,49 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
// ---- Order By Clause
// all SortKeys should be contained by limitSet
private OrderByComponent parseOrderByClause(
- IoTDBSqlParser.OrderByClauseContext ctx, ImmutableSet<SortKey> limitSet) {
+ IoTDBSqlParser.OrderByClauseContext ctx, ImmutableSet<String> limitSet) {
OrderByComponent orderByComponent = new OrderByComponent();
- Set<SortKey> sortKeySet = new HashSet<>();
+ Set<String> sortKeySet = new HashSet<>();
for (IoTDBSqlParser.OrderByAttributeClauseContext orderByAttributeClauseContext :
ctx.orderByAttributeClause()) {
- SortItem sortItem = parseOrderByAttributeClause(orderByAttributeClauseContext);
-
- SortKey sortKey = sortItem.getSortKey();
- if (!limitSet.contains(sortKey)) {
- throw new SemanticException(
- String.format("ORDER BY: sort key[%s] is not contained in '%s'", sortKey, limitSet));
+ // if the order by clause is unique, then the following sort keys will be ignored
+ if (orderByComponent.isUnique()) {
+ break;
}
+ SortItem sortItem = parseOrderByAttributeClause(orderByAttributeClauseContext, limitSet);
+
+ String sortKey = sortItem.getSortKey();
if (sortKeySet.contains(sortKey)) {
- throw new SemanticException(String.format("ORDER BY: duplicate sort key '%s'", sortKey));
+ continue;
} else {
sortKeySet.add(sortKey);
+ }
+
+ if (sortItem.isExpression()) {
+ orderByComponent.addExpressionSortItem(sortItem);
+ } else {
orderByComponent.addSortItem(sortItem);
}
}
return orderByComponent;
}
- private SortItem parseOrderByAttributeClause(IoTDBSqlParser.OrderByAttributeClauseContext ctx) {
- return new SortItem(
- SortKey.valueOf(ctx.sortKey().getText().toUpperCase()),
- ctx.DESC() != null ? Ordering.DESC : Ordering.ASC);
+ private SortItem parseOrderByAttributeClause(
+ IoTDBSqlParser.OrderByAttributeClauseContext ctx, ImmutableSet<String> limitSet) {
+ if (ctx.sortKey() != null) {
+ String sortKey = ctx.sortKey().getText().toUpperCase();
+ if (!limitSet.contains(sortKey)) {
+ throw new SemanticException(
+ String.format("ORDER BY: sort key[%s] is not contained in '%s'", sortKey, limitSet));
+ }
+ return new SortItem(sortKey, ctx.DESC() != null ? Ordering.DESC : Ordering.ASC);
+ } else {
+ Expression sortExpression = parseExpression(ctx.expression(), true);
+ return new SortItem(
+ sortExpression,
+ ctx.DESC() != null ? Ordering.DESC : Ordering.ASC,
+ ctx.FIRST() != null ? NullOrdering.FIRST : NullOrdering.LAST);
+ }
}
// ---- Fill Clause
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
index e36e29aece..8ac7b9ac8b 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
@@ -90,6 +90,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.OrderByParameter;
import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
+import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
import org.apache.iotdb.db.mpp.plan.statement.sys.ShowQueriesStatement;
import org.apache.iotdb.db.utils.SchemaUtils;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -100,10 +101,10 @@ import org.apache.commons.lang.Validate;
import java.time.ZoneId;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -535,56 +536,32 @@ public class LogicalPlanBuilder {
Map<String, PlanNode> deviceNameToSourceNodesMap,
Set<Expression> deviceViewOutputExpressions,
Map<String, List<Integer>> deviceToMeasurementIndexesMap,
- List<SortItem> sortItemList) {
+ Set<Expression> selectExpression,
+ QueryStatement queryStatement) {
List<String> outputColumnNames =
deviceViewOutputExpressions.stream()
.map(Expression::getExpressionString)
.collect(Collectors.toList());
- int timePriority = -1, devicePriority = -1;
- for (int i = 0; i < sortItemList.size(); i++) {
- SortKey sortKey = sortItemList.get(i).getSortKey();
- if (sortKey == SortKey.TIME) {
- timePriority = sortItemList.size() - i;
- } else if (sortKey == SortKey.DEVICE) {
- devicePriority = sortItemList.size() - i;
- }
+ List<SortItem> sortItemList = queryStatement.getSortItemList();
+
+ if (sortItemList.isEmpty()) {
+ sortItemList = new ArrayList<>();
+ }
+ if (!queryStatement.isOrderByDevice()) {
+ sortItemList.add(new SortItem(SortKey.DEVICE, Ordering.ASC));
+ }
+ if (!queryStatement.isOrderByTime()) {
+ sortItemList.add(new SortItem(SortKey.TIME, Ordering.ASC));
}
- Ordering deviceOrdering =
- devicePriority == -1
- ? Ordering.ASC
- : sortItemList.get(sortItemList.size() - devicePriority).getOrdering();
- Ordering timeOrdering =
- timePriority == -1
- ? Ordering.ASC
- : sortItemList.get(sortItemList.size() - timePriority).getOrdering();
-
- if ((timePriority == -1 && devicePriority == -1) || devicePriority > timePriority) {
- DeviceViewNode deviceViewNode =
- new DeviceViewNode(
- context.getQueryId().genPlanNodeId(),
- new OrderByParameter(
- Arrays.asList(
- new SortItem(SortKey.DEVICE, deviceOrdering),
- new SortItem(SortKey.TIME, timeOrdering))),
- outputColumnNames,
- deviceToMeasurementIndexesMap);
- for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
- String deviceName = entry.getKey();
- PlanNode subPlan = entry.getValue();
- deviceViewNode.addChildDeviceNode(deviceName, subPlan);
- }
- this.root = deviceViewNode;
- } else {
+ OrderByParameter orderByParameter = new OrderByParameter(sortItemList);
+
+ // order by time, device can be optimized by SingleDeviceViewNode and MergeSortNode
+ if (queryStatement.isOrderByBasedOnTime() && !queryStatement.hasOrderByExpression()) {
MergeSortNode mergeSortNode =
new MergeSortNode(
- context.getQueryId().genPlanNodeId(),
- new OrderByParameter(
- Arrays.asList(
- new SortItem(SortKey.TIME, timeOrdering),
- new SortItem(SortKey.DEVICE, deviceOrdering))),
- outputColumnNames);
+ context.getQueryId().genPlanNodeId(), orderByParameter, outputColumnNames);
for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
String deviceName = entry.getKey();
PlanNode subPlan = entry.getValue();
@@ -598,10 +575,38 @@ public class LogicalPlanBuilder {
mergeSortNode.addChild(singleDeviceViewNode);
}
this.root = mergeSortNode;
+ } else {
+ DeviceViewNode deviceViewNode =
+ new DeviceViewNode(
+ context.getQueryId().genPlanNodeId(),
+ orderByParameter,
+ outputColumnNames,
+ deviceToMeasurementIndexesMap);
+
+ for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
+ String deviceName = entry.getKey();
+ PlanNode subPlan = entry.getValue();
+ deviceViewNode.addChildDeviceNode(deviceName, subPlan);
+ }
+ this.root = deviceViewNode;
}
context.getTypeProvider().setType(DEVICE, TSDataType.TEXT);
updateTypeProvider(deviceViewOutputExpressions);
+
+ if (queryStatement.needPushDownSort()) {
+ if (selectExpression.size() != deviceViewOutputExpressions.size()) {
+ this.root =
+ new TransformNode(
+ context.getQueryId().genPlanNodeId(),
+ root,
+ selectExpression.toArray(new Expression[0]),
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ }
+ }
+
return this;
}
@@ -906,17 +911,24 @@ public class LogicalPlanBuilder {
return this;
}
- public LogicalPlanBuilder planHaving(
+ public LogicalPlanBuilder planHavingAndTransform(
Expression havingExpression,
Set<Expression> selectExpressions,
+ Set<Expression> orderByExpression,
boolean isGroupByTime,
ZoneId zoneId,
Ordering scanOrder) {
+
+ Set<Expression> outputExpressions = new HashSet<>(selectExpressions);
+ if (orderByExpression != null) {
+ outputExpressions.addAll(orderByExpression);
+ }
+
if (havingExpression != null) {
return planFilterAndTransform(
- havingExpression, selectExpressions, isGroupByTime, zoneId, scanOrder);
+ havingExpression, outputExpressions, isGroupByTime, zoneId, scanOrder);
} else {
- return planTransform(selectExpressions, isGroupByTime, zoneId, scanOrder);
+ return planTransform(outputExpressions, isGroupByTime, zoneId, scanOrder);
}
}
@@ -1200,4 +1212,45 @@ public class LogicalPlanBuilder {
this.root = new ShowQueriesNode(context.getQueryId().genPlanNodeId(), dataNodeLocation);
return this;
}
+
+ public LogicalPlanBuilder planOrderBy(
+ Set<Expression> orderByExpressions, List<SortItem> sortItemList) {
+
+ updateTypeProvider(orderByExpressions);
+ OrderByParameter orderByParameter = new OrderByParameter(sortItemList);
+ if (orderByParameter.isEmpty()) {
+ return this;
+ }
+ this.root = new SortNode(context.getQueryId().genPlanNodeId(), root, orderByParameter);
+ return this;
+ }
+
+ public LogicalPlanBuilder planOrderBy(
+ QueryStatement queryStatement,
+ Set<Expression> orderByExpressions,
+ Set<Expression> selectExpression) {
+ // only the order by clause having expression needs a sortNode
+ if (!queryStatement.hasOrderByExpression()) {
+ return this;
+ }
+
+ updateTypeProvider(orderByExpressions);
+ OrderByParameter orderByParameter = new OrderByParameter(queryStatement.getSortItemList());
+ if (orderByParameter.isEmpty()) {
+ return this;
+ }
+ this.root = new SortNode(context.getQueryId().genPlanNodeId(), root, orderByParameter);
+
+ if (root.getOutputColumnNames().size() != selectExpression.size()) {
+ this.root =
+ new TransformNode(
+ context.getQueryId().genPlanNodeId(),
+ root,
+ selectExpression.toArray(new Expression[0]),
+ queryStatement.isGroupByTime(),
+ queryStatement.getSelectComponent().getZoneId(),
+ queryStatement.getResultTimeOrder());
+ }
+ return this;
+ }
}
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 8895c253c1..a8cb30fac9 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
@@ -144,6 +144,13 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
: null,
analysis.getDeviceViewInputIndexesMap().get(deviceName),
context));
+ // sortOperator push down
+ if (queryStatement.needPushDownSort()) {
+ subPlanBuilder =
+ subPlanBuilder.planOrderBy(
+ analysis.getDeviceToOrderByExpressions().get(deviceName),
+ analysis.getDeviceToSortItems().get(deviceName));
+ }
deviceToSubPlanMap.put(deviceName, subPlanBuilder.getRoot());
}
// convert to ALIGN BY DEVICE view
@@ -152,7 +159,8 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
deviceToSubPlanMap,
analysis.getDeviceViewOutputExpressions(),
analysis.getDeviceViewInputIndexesMap(),
- queryStatement.getSortItemList());
+ analysis.getSelectExpressions(),
+ queryStatement);
} else {
planBuilder =
planBuilder.withNewRoot(
@@ -169,14 +177,21 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
if (queryStatement.isAggregationQuery()) {
planBuilder =
- planBuilder.planHaving(
+ planBuilder.planHavingAndTransform(
analysis.getHavingExpression(),
analysis.getSelectExpressions(),
+ analysis.getOrderByExpressions(),
queryStatement.isGroupByTime(),
queryStatement.getSelectComponent().getZoneId(),
queryStatement.getResultTimeOrder());
}
+ if (!queryStatement.needPushDownSort()) {
+ planBuilder =
+ planBuilder.planOrderBy(
+ queryStatement, analysis.getOrderByExpressions(), analysis.getSelectExpressions());
+ }
+
// other upstream node
planBuilder =
planBuilder
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
index 5c79bfcdd9..d51bedfd86 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
@@ -797,7 +797,7 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
TimeSelector selector = null;
TimeComparator timeComparator = null;
for (SortItem sortItem : node.getMergeOrderParameter().getSortItemList()) {
- if (sortItem.getSortKey() == SortKey.TIME) {
+ if (Objects.equals(sortItem.getSortKey(), SortKey.TIME)) {
Ordering ordering = sortItem.getOrdering();
if (ordering == Ordering.ASC) {
selector = new TimeSelector(node.getChildren().size() << 1, true);
@@ -853,12 +853,12 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
List<TSDataType> sortItemDataTypeList) {
sortItemList.forEach(
sortItem -> {
- if (sortItem.getSortKey() == SortKey.TIME) {
+ if (sortItem.getSortKey().equals(SortKey.TIME)) {
sortItemIndexList.add(-1);
sortItemDataTypeList.add(TSDataType.INT64);
} else {
for (int i = 0; i < outputColumnNames.size(); i++) {
- if (sortItem.getSortKey().toString().equalsIgnoreCase(outputColumnNames.get(i))) {
+ if (sortItem.getSortKey().equalsIgnoreCase(outputColumnNames.get(i))) {
sortItemIndexList.add(i);
sortItemDataTypeList.add(dataTypes.get(i));
break;
@@ -1601,7 +1601,8 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
.addOperatorContext(
context.getNextOperatorId(),
node.getPlanNodeId(),
- DeviceViewIntoOperator.class.getSimpleName());
+ SortOperator.class.getSimpleName());
+
List<TSDataType> dataTypes = getOutputColumnTypes(node, context.getTypeProvider());
List<SortItem> sortItemList = node.getOrderByParameter().getSortItemList();
@@ -2201,7 +2202,8 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
List<SortItem> sortItemList = node.getMergeOrderParameter().getSortItemList();
checkArgument(
sortItemList.isEmpty()
- || (sortItemList.size() == 1 && sortItemList.get(0).getSortKey() == SortKey.TIMESERIES),
+ || (sortItemList.size() == 1
+ && Objects.equals(sortItemList.get(0).getSortKey(), SortKey.TIMESERIES)),
"Last query only support order by timeseries asc/desc");
context.setLastQueryTimeFilter(node.getTimeFilter());
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
index 2f1e85aedf..8d35c5290f 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
@@ -40,7 +40,6 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.IdentitySinkNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.MultiChildrenSinkNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.ShuffleSinkNode;
import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
-import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
import org.apache.commons.lang3.Validate;
@@ -164,8 +163,8 @@ public class DistributionPlanner {
OrderByComponent orderByComponent = queryStatement.getOrderByComponent();
return nodeGroupContext.isAlignByDevice()
&& orderByComponent != null
- && !(orderByComponent.getSortItemList().isEmpty()
- || orderByComponent.getSortItemList().get(0).getSortKey().equals(SortKey.DEVICE));
+ && (!orderByComponent.getSortItemList().isEmpty()
+ && (orderByComponent.isBasedOnTime() && !queryStatement.hasOrderByExpression()));
}
public PlanNode optimize(PlanNode rootWithExchange) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
index 56dfda1cd7..edd96379b1 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
@@ -44,6 +44,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MergeSortNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MultiChildProcessNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SingleDeviceViewNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SlidingWindowAggregationNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SortNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TimeJoinNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TransformNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryCollectNode;
@@ -269,6 +270,11 @@ public class ExchangeNodeAdder extends PlanVisitor<PlanNode, NodeGroupContext> {
return processMultiChildNode(node, context);
}
+ @Override
+ public PlanNode visitSort(SortNode node, NodeGroupContext context) {
+ return processOneChildNode(node, context);
+ }
+
private PlanNode processMultiChildNode(MultiChildProcessNode node, NodeGroupContext context) {
if (analysis.isVirtualSource()) {
return processMultiChildNodeByLocation(node, context);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
index 16771c6ad1..9bff84a243 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
@@ -44,6 +44,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MergeSortNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MultiChildProcessNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SingleDeviceViewNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SlidingWindowAggregationNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SortNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TimeJoinNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryCollectNode;
import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryMergeNode;
@@ -195,6 +196,10 @@ public class SourceRewriter extends SimplePlanNodeRewriter<DistributionPlanConte
return deviceViewNodeList;
}
+ if (analysis.isOrderByExpressionInDeviceView()) {
+ return deviceViewNodeList;
+ }
+
MergeSortNode mergeSortNode =
new MergeSortNode(
context.queryContext.getQueryId().genPlanNodeId(),
@@ -264,6 +269,38 @@ public class SourceRewriter extends SimplePlanNodeRewriter<DistributionPlanConte
}
}
+ @Override
+ public List<PlanNode> visitSort(SortNode node, DistributionPlanContext context) {
+
+ if (node.getChild() instanceof DeviceViewNode) {
+ analysis.setOrderByExpressionInDeviceView(true);
+ }
+
+ List<PlanNode> children = rewrite(node.getChild(), context);
+ if (children.size() == 1) {
+ node.setChild(children.get(0));
+ return Collections.singletonList(node);
+ }
+
+ MergeSortNode mergeSortNode =
+ new MergeSortNode(
+ context.queryContext.getQueryId().genPlanNodeId(),
+ node.getOrderByParameter(),
+ node.getOutputColumnNames());
+
+ for (PlanNode child : children) {
+ SortNode sortNode = cloneSortNodeWithOutChild(node, context);
+ sortNode.setChild(child);
+ mergeSortNode.addChild(sortNode);
+ }
+ return Collections.singletonList(mergeSortNode);
+ }
+
+ private SortNode cloneSortNodeWithOutChild(SortNode node, DistributionPlanContext context) {
+ return new SortNode(
+ context.queryContext.getQueryId().genPlanNodeId(), node.getOrderByParameter());
+ }
+
@Override
public List<PlanNode> visitSchemaQueryMerge(
SchemaQueryMergeNode node, DistributionPlanContext context) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
similarity index 79%
copy from server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
copy to server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
index b39d16fbba..0821fbfdc3 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
@@ -19,12 +19,15 @@
package org.apache.iotdb.db.mpp.plan.statement.component;
-public enum SortKey {
- TIME,
- TIMESERIES,
- DEVICE,
- QUERYID,
- DATANODEID,
- ELAPSEDTIME,
- STATEMENT
+public enum NullOrdering {
+ FIRST,
+ LAST;
+
+ public boolean isFirst() {
+ return this == FIRST;
+ }
+
+ public NullOrdering reverse() {
+ return this == NullOrdering.FIRST ? NullOrdering.LAST : NullOrdering.FIRST;
+ }
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
index a3784773ad..5847d07917 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
@@ -19,6 +19,7 @@
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;
import java.util.ArrayList;
@@ -30,6 +31,7 @@ import static com.google.common.base.Preconditions.checkState;
public class OrderByComponent extends StatementNode {
private final List<SortItem> sortItemList;
+ private final List<Expression> sortItemExpressionList;
private boolean orderByTime = false;
private int timeOrderPriority = -1;
@@ -42,26 +44,54 @@ public class OrderByComponent extends StatementNode {
public OrderByComponent() {
this.sortItemList = new ArrayList<>();
+ this.sortItemExpressionList = new ArrayList<>();
}
public void addSortItem(SortItem sortItem) {
this.sortItemList.add(sortItem);
- if (sortItem.getSortKey() == SortKey.TIME) {
- orderByTime = true;
- timeOrderPriority = sortItemList.size() - 1;
- } else if (sortItem.getSortKey() == SortKey.TIMESERIES) {
- orderByTimeseries = true;
- timeseriesOrderPriority = sortItemList.size() - 1;
- } else {
- orderByDevice = true;
- deviceOrderPriority = sortItemList.size() - 1;
+ switch (sortItem.getSortKey()) {
+ case SortKey.TIME:
+ orderByTime = true;
+ timeOrderPriority = sortItemList.size() - 1;
+ break;
+ case SortKey.TIMESERIES:
+ orderByTimeseries = true;
+ timeseriesOrderPriority = sortItemList.size() - 1;
+ break;
+ case SortKey.DEVICE:
+ orderByDevice = true;
+ deviceOrderPriority = sortItemList.size() - 1;
+ break;
}
}
+ // if the sortItem can specify one unique time series
+ public boolean isUnique() {
+ return orderByDevice && orderByTime;
+ }
+
+ // if the first sortItem is device
+ public boolean isBasedOnDevice() {
+ return orderByDevice && deviceOrderPriority == 0;
+ }
+
+ public boolean isBasedOnTime() {
+ return orderByTime && timeOrderPriority == 0;
+ }
+
+ public void addExpressionSortItem(SortItem sortItem) {
+ this.sortItemList.add(sortItem);
+ this.sortItemExpressionList.add(sortItem.getExpression());
+ }
+
public List<SortItem> getSortItemList() {
return sortItemList;
}
+ public List<Expression> getExpressionSortItemList() {
+ return sortItemExpressionList;
+ }
+
public boolean isOrderByTime() {
return orderByTime;
}
@@ -89,6 +119,14 @@ public class OrderByComponent extends StatementNode {
return sortItemList.get(deviceOrderPriority).getOrdering();
}
+ public boolean isDeviceOrderInitialized() {
+ return deviceOrderPriority != -1;
+ }
+
+ public boolean isTimeOrderInitialized() {
+ return timeOrderPriority != -1;
+ }
+
public String toSQLString() {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("ORDER BY ");
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
index 71e169b22d..8b9612f566 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.mpp.plan.statement.component;
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
@@ -28,15 +29,31 @@ import java.util.Objects;
public class SortItem {
- private final SortKey sortKey;
+ private String sortKey;
private final Ordering ordering;
+ private final NullOrdering nullOrdering;
- public SortItem(SortKey sortKey, Ordering ordering) {
+ // only used in analyzing period
+ private Expression expression;
+
+ public SortItem(String sortKey, Ordering ordering) {
+ this(sortKey, ordering, NullOrdering.LAST);
+ }
+
+ public SortItem(String sortKey, Ordering ordering, NullOrdering nullOrdering) {
this.sortKey = sortKey;
this.ordering = ordering;
+ this.nullOrdering = nullOrdering;
}
- public SortKey getSortKey() {
+ public SortItem(Expression expression, Ordering ordering, NullOrdering nullOrdering) {
+ this.expression = expression;
+ this.ordering = ordering;
+ this.nullOrdering = nullOrdering;
+ this.sortKey = expression.getExpressionString();
+ }
+
+ public String getSortKey() {
return sortKey;
}
@@ -44,24 +61,44 @@ public class SortItem {
return ordering;
}
+ public NullOrdering getNullOrdering() {
+ return nullOrdering;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ this.sortKey = expression.getExpressionString();
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public boolean isExpression() {
+ return expression != null;
+ }
+
public SortItem reverse() {
- return new SortItem(getSortKey(), getOrdering().reverse());
+ return new SortItem(getSortKey(), getOrdering().reverse(), getNullOrdering().reverse());
}
public void serialize(ByteBuffer byteBuffer) {
- ReadWriteIOUtils.write(sortKey.ordinal(), byteBuffer);
+ ReadWriteIOUtils.write(sortKey, byteBuffer);
ReadWriteIOUtils.write(ordering.ordinal(), byteBuffer);
+ ReadWriteIOUtils.write(nullOrdering.ordinal(), byteBuffer);
}
public void serialize(DataOutputStream stream) throws IOException {
- ReadWriteIOUtils.write(sortKey.ordinal(), stream);
+ ReadWriteIOUtils.write(sortKey, stream);
ReadWriteIOUtils.write(ordering.ordinal(), stream);
+ ReadWriteIOUtils.write(nullOrdering.ordinal(), stream);
}
public static SortItem deserialize(ByteBuffer byteBuffer) {
- SortKey sortKey = SortKey.values()[ReadWriteIOUtils.readInt(byteBuffer)];
+ String sortKey = ReadWriteIOUtils.readString(byteBuffer);
Ordering ordering = Ordering.values()[ReadWriteIOUtils.readInt(byteBuffer)];
- return new SortItem(sortKey, ordering);
+ NullOrdering nullOrdering = NullOrdering.values()[ReadWriteIOUtils.readInt(byteBuffer)];
+ return new SortItem(sortKey, ordering, nullOrdering);
}
@Override
@@ -73,15 +110,17 @@ public class SortItem {
return false;
}
SortItem sortItem = (SortItem) o;
- return sortKey == sortItem.sortKey && ordering == sortItem.ordering;
+ return Objects.equals(sortKey, sortItem.sortKey)
+ && ordering == sortItem.ordering
+ && nullOrdering == sortItem.nullOrdering;
}
@Override
public int hashCode() {
- return Objects.hash(sortKey, ordering);
+ return Objects.hash(sortKey, ordering, nullOrdering);
}
public String toSQLString() {
- return getSortKey().toString() + " " + getOrdering().toString();
+ return getSortKey() + " " + getOrdering().toString() + " " + getNullOrdering().toString();
}
}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
index b39d16fbba..650f6a3178 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
@@ -19,12 +19,12 @@
package org.apache.iotdb.db.mpp.plan.statement.component;
-public enum SortKey {
- TIME,
- TIMESERIES,
- DEVICE,
- QUERYID,
- DATANODEID,
- ELAPSEDTIME,
- STATEMENT
+public class SortKey {
+ public static final String TIME = "TIME";
+ public static final String TIMESERIES = "TIMESERIES";
+ public static final String DEVICE = "DEVICE";
+ public static final String QUERYID = "QUERYID";
+ public static final String DATANODEID = "DATANODEID";
+ public static final String ELAPSEDTIME = "ELAPSEDTIME";
+ public static final String STATEMENT = "STATEMENT";
}
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 9f6d158037..00ecae2d99 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
@@ -311,6 +311,10 @@ public class QueryStatement extends Statement {
return isGroupByVariation() || isGroupByCondition() || isGroupByCount();
}
+ public boolean hasOrderByExpression() {
+ return !getExpressionSortItemList().isEmpty();
+ }
+
public boolean isAlignByTime() {
return resultSetFormat == ResultSetFormat.ALIGN_BY_TIME;
}
@@ -357,6 +361,19 @@ public class QueryStatement extends Statement {
return orderByComponent.getDeviceOrder();
}
+ // push down only support raw data query currently
+ public boolean needPushDownSort() {
+ return !isAggregationQuery() && hasOrderByExpression() && isOrderByBasedOnDevice();
+ }
+
+ public boolean isOrderByBasedOnDevice() {
+ return orderByComponent != null && orderByComponent.isBasedOnDevice();
+ }
+
+ public boolean isOrderByBasedOnTime() {
+ return orderByComponent != null && orderByComponent.isBasedOnTime();
+ }
+
public List<SortItem> getSortItemList() {
if (orderByComponent == null) {
return Collections.emptyList();
@@ -364,6 +381,45 @@ public class QueryStatement extends Statement {
return orderByComponent.getSortItemList();
}
+ public List<Expression> getExpressionSortItemList() {
+ if (orderByComponent == null) {
+ return Collections.emptyList();
+ }
+ return orderByComponent.getExpressionSortItemList();
+ }
+
+ // update the sortItems with expressionSortItems
+ public void updateSortItems(Set<Expression> orderByExpressions) {
+ Expression[] sortItemExpressions = orderByExpressions.toArray(new Expression[0]);
+ List<SortItem> sortItems = getSortItemList();
+ int expressionIndex = 0;
+ for (int i = 0; i < sortItems.size() && expressionIndex < sortItemExpressions.length; i++) {
+ SortItem sortItem = sortItems.get(i);
+ if (sortItem.isExpression()) {
+ sortItem.setExpression(sortItemExpressions[expressionIndex]);
+ expressionIndex++;
+ }
+ }
+ }
+
+ public List<SortItem> getUpdatedSortItems(Set<Expression> orderByExpressions) {
+ Expression[] sortItemExpressions = orderByExpressions.toArray(new Expression[0]);
+ List<SortItem> sortItems = getSortItemList();
+ List<SortItem> newSortItems = new ArrayList<>();
+ int expressionIndex = 0;
+ for (int i = 0; i < sortItems.size() && expressionIndex < sortItemExpressions.length; i++) {
+ SortItem sortItem = sortItems.get(i);
+ SortItem newSortItem =
+ new SortItem(sortItem.getSortKey(), sortItem.getOrdering(), sortItem.getNullOrdering());
+ if (sortItem.isExpression()) {
+ newSortItem.setExpression(sortItemExpressions[expressionIndex]);
+ expressionIndex++;
+ }
+ newSortItems.add(newSortItem);
+ }
+ return newSortItems;
+ }
+
public boolean hasFill() {
return fillComponent != null;
}
@@ -416,6 +472,23 @@ public class QueryStatement extends Statement {
? resultColumn.getAlias()
: resultColumn.getExpression().getExpressionString());
}
+ for (Expression expression : getExpressionSortItemList()) {
+ if (expression instanceof FunctionExpression) {
+ if (!expression.isBuiltInAggregationFunctionExpression()) {
+ throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+ }
+ } else {
+ if (expression instanceof TimeSeriesOperand) {
+ throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+ }
+ for (Expression subExpression : expression.getExpressions()) {
+ if (!subExpression.isBuiltInAggregationFunctionExpression()) {
+ throw new SemanticException(
+ "Raw data and aggregation hybrid query is not supported.");
+ }
+ }
+ }
+ }
if (isGroupByTag()) {
if (hasHaving()) {
throw new SemanticException("Having clause is not supported yet in GROUP BY TAGS query");
@@ -443,6 +516,13 @@ public class QueryStatement extends Statement {
throw new SemanticException(
"Common queries and aggregated queries are not allowed to appear at the same time");
}
+ for (Expression expression : getExpressionSortItemList()) {
+ for (Expression subExpression : expression.getExpressions()) {
+ if (subExpression.isBuiltInAggregationFunctionExpression()) {
+ throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+ }
+ }
+ }
}
if (hasWhere()) {
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
index c4e1dae0d3..a8c51f77f8 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
@@ -328,8 +328,8 @@ public class MergeSortOperatorTest {
Arrays.asList(
new SortItem(SortKey.TIME, timeOrdering),
new SortItem(SortKey.DEVICE, deviceOrdering)),
- null,
- null));
+ Arrays.asList(-1, 0),
+ Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
mergeSortOperator
.getOperatorContext()
.setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -796,8 +796,8 @@ public class MergeSortOperatorTest {
Arrays.asList(
new SortItem(SortKey.TIME, timeOrdering),
new SortItem(SortKey.DEVICE, deviceOrdering)),
- null,
- null));
+ Arrays.asList(-1, 0),
+ Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
mergeSortOperator1
.getOperatorContext()
.setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -810,8 +810,8 @@ public class MergeSortOperatorTest {
Arrays.asList(
new SortItem(SortKey.TIME, timeOrdering),
new SortItem(SortKey.DEVICE, deviceOrdering)),
- null,
- null));
+ Arrays.asList(-1, 0),
+ Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
mergeSortOperator2
.getOperatorContext()
.setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -825,8 +825,8 @@ public class MergeSortOperatorTest {
Arrays.asList(
new SortItem(SortKey.TIME, timeOrdering),
new SortItem(SortKey.DEVICE, deviceOrdering)),
- null,
- null));
+ Arrays.asList(-1, 0),
+ Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
mergeSortOperator
.getOperatorContext()
.setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -1269,8 +1269,8 @@ public class MergeSortOperatorTest {
Arrays.asList(
new SortItem(SortKey.DEVICE, deviceOrdering),
new SortItem(SortKey.TIME, timeOrdering)),
- null,
- null));
+ Arrays.asList(0, -1),
+ Arrays.asList(TSDataType.TEXT, TSDataType.INT64)));
mergeSortOperator
.getOperatorContext()
.setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
index ac16f04ec3..670553df0f 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
@@ -758,6 +758,179 @@ public class AnalyzeTest {
}
}
+ @Test
+ public void testOrderByAnalyze() {
+ String sql = "select s1 from root.sg.d1 order by root.sg.d1.s1 + root.sg.d1.s2";
+ String sql2 = "select avg(s1) from root.sg.d1 order by avg(root.sg.d1.s1) + avg(root.sg.d1.s2)";
+ try {
+ Analysis actualAnalysis = analyzeSQL(sql);
+ Analysis expectedAnalysis = new Analysis();
+ expectedAnalysis.setOrderByExpressions(
+ Sets.newHashSet(
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))));
+ expectedAnalysis.setSourceTransformExpressions(
+ Sets.newHashSet(
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1"))));
+ expectedAnalysis.setSourceExpressions(
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+ assert actualAnalysis != null;
+ orderByAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+
+ actualAnalysis = analyzeSQL(sql2);
+ expectedAnalysis = new Analysis();
+ expectedAnalysis.setOrderByExpressions(
+ Sets.newHashSet(
+ new AdditionExpression(
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))))));
+ expectedAnalysis.setAggregationExpressions(
+ Sets.newHashSet(
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))));
+ expectedAnalysis.setSourceTransformExpressions(
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+ expectedAnalysis.setSourceExpressions(
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+ assert actualAnalysis != null;
+ orderByAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(sql + ", " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAlignByDeviceOrderByAnalyze() {
+ String sql = "select s1 from root.sg.d1 order by s1 + s2 align by device";
+ String sql2 = "select avg(s1) from root.sg.d1 order by avg(s1) + avg(s2) align by device";
+ try {
+ Analysis actualAnalysis = analyzeSQL(sql);
+ Analysis expectedAnalysis = new Analysis();
+ expectedAnalysis.setOrderByExpressions(
+ Sets.newHashSet(
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("s1")),
+ new TimeSeriesOperand(new PartialPath("s2")))));
+ expectedAnalysis.setDeviceToOrderByExpressions(
+ ImmutableMap.of(
+ "root.sg.d1",
+ Sets.newHashSet(
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))));
+ expectedAnalysis.setDeviceToSourceTransformExpressions(
+ ImmutableMap.of(
+ "root.sg.d1",
+ Sets.newHashSet(
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))));
+ expectedAnalysis.setDeviceToAggregationExpressions(ImmutableMap.of());
+ expectedAnalysis.setDeviceViewOutputExpressions(
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("Device")),
+ new TimeSeriesOperand(new PartialPath("s1")),
+ new AdditionExpression(
+ new TimeSeriesOperand(new PartialPath("s1")),
+ new TimeSeriesOperand(new PartialPath("s2")))));
+ assert actualAnalysis != null;
+ orderByAlignByDeviceAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+
+ actualAnalysis = analyzeSQL(sql2);
+ expectedAnalysis = new Analysis();
+ expectedAnalysis.setOrderByExpressions(
+ Sets.newHashSet(
+ new AdditionExpression(
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(new TimeSeriesOperand(new PartialPath("s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(new TimeSeriesOperand(new PartialPath("s2")))))));
+ expectedAnalysis.setDeviceToOrderByExpressions(
+ ImmutableMap.of(
+ "root.sg.d1",
+ Sets.newHashSet(
+ new AdditionExpression(
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))))));
+ expectedAnalysis.setDeviceToAggregationExpressions(
+ ImmutableMap.of(
+ "root.sg.d1",
+ Sets.newHashSet(
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))))));
+ expectedAnalysis.setDeviceToSourceTransformExpressions(
+ ImmutableMap.of(
+ "root.sg.d1",
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))));
+ expectedAnalysis.setDeviceViewOutputExpressions(
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("Device")),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(new TimeSeriesOperand(new PartialPath("s1")))),
+ new FunctionExpression(
+ "avg",
+ new LinkedHashMap<>(),
+ Collections.singletonList(new TimeSeriesOperand(new PartialPath("s2"))))));
+ assert actualAnalysis != null;
+ orderByAlignByDeviceAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(sql + ", " + e.getMessage());
+ }
+ }
+
@Test
public void testAlias1() {
Analysis analysis = analyzeSQL("select s1 as a, s2 as b from root.sg.d2.a, root.sg.d2.b");
@@ -853,4 +1026,31 @@ public class AnalyzeTest {
assertEquals(expectedAnalysis.getRespDatasetHeader(), actualAnalysis.getRespDatasetHeader());
assertEquals(expectedAnalysis.getGlobalTimeFilter(), actualAnalysis.getGlobalTimeFilter());
}
+
+ private void orderByAnalysisEqualTest(Analysis actualAnalysis, Analysis expectedAnalysis) {
+ assertEquals(expectedAnalysis.getOrderByExpressions(), actualAnalysis.getOrderByExpressions());
+ assertEquals(
+ expectedAnalysis.getAggregationExpressions(), actualAnalysis.getAggregationExpressions());
+ assertEquals(
+ expectedAnalysis.getSourceTransformExpressions(),
+ actualAnalysis.getSourceTransformExpressions());
+ assertEquals(expectedAnalysis.getSourceExpressions(), actualAnalysis.getSourceExpressions());
+ }
+
+ private void orderByAlignByDeviceAnalysisEqualTest(
+ Analysis actualAnalysis, Analysis expectedAnalysis) {
+ assertEquals(
+ expectedAnalysis.getDeviceViewOutputExpressions(),
+ actualAnalysis.getDeviceViewOutputExpressions());
+ assertEquals(
+ expectedAnalysis.getDeviceToOrderByExpressions(),
+ actualAnalysis.getDeviceToOrderByExpressions());
+ assertEquals(expectedAnalysis.getOrderByExpressions(), actualAnalysis.getOrderByExpressions());
+ assertEquals(
+ expectedAnalysis.getDeviceToAggregationExpressions(),
+ actualAnalysis.getDeviceToAggregationExpressions());
+ assertEquals(
+ expectedAnalysis.getDeviceToSourceTransformExpressions(),
+ actualAnalysis.getDeviceToSourceTransformExpressions());
+ }
}
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
index a7dc943280..f4b88db27c 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
@@ -143,4 +143,21 @@ public enum TSDataType {
throw new UnSupportedDataTypeException(this.toString());
}
}
+
+ /** @return whether a comparable datatype */
+ public boolean isComparable() {
+ switch (this) {
+ case INT32:
+ case INT64:
+ case FLOAT:
+ case DOUBLE:
+ case TEXT:
+ case BOOLEAN:
+ return true;
+ case VECTOR:
+ return false;
+ default:
+ throw new UnSupportedDataTypeException(this.toString());
+ }
+ }
}