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 f98b3d218e0 Fix LIMIT/OFFSET push down in GROUP BY MONTH
f98b3d218e0 is described below

commit f98b3d218e0bcc94f8277ce4db51525fa49ea8b7
Author: YangCaiyin <[email protected]>
AuthorDate: Mon Dec 18 19:20:22 2023 +0800

    Fix LIMIT/OFFSET push down in GROUP BY MONTH
---
 .../apache/iotdb/db/it/groupby/IOTDBGroupByIT.java | 57 +++++++++++++
 .../db/it/groupby/IoTDBGroupByNaturalMonthIT.java  | 44 ++++++----
 .../IoTDBGroupByNaturalMonthNsPrecisionIT.java     | 12 +--
 .../IoTDBGroupByNaturalMonthUsPrecisionIT.java     | 12 +--
 .../iotdb/db/protocol/session/SessionManager.java  |  2 +-
 .../timerangeiterator/AggrWindowIterator.java      | 39 ++++++---
 .../plan/optimization/LimitOffsetPushDown.java     | 59 +++++++++++++-
 .../plan/planner/LogicalPlanBuilder.java           | 11 ++-
 .../plan/planner/LogicalPlanVisitor.java           |  3 +-
 .../org/apache/iotdb/db/utils/DateTimeUtils.java   | 12 +--
 .../aggregation/TimeRangeIteratorTest.java         | 66 +++++++--------
 .../read/filter/operator/GroupByMonthFilter.java   | 11 ++-
 .../apache/iotdb/tsfile/utils/TimeDuration.java    | 93 ++++++++++------------
 .../iotdb/tsfile/utils/TimeDurationTest.java       | 40 ++++++----
 14 files changed, 299 insertions(+), 162 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IOTDBGroupByIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IOTDBGroupByIT.java
index 7fa8fdc3f81..1c9d5dfa6ca 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IOTDBGroupByIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IOTDBGroupByIT.java
@@ -733,4 +733,61 @@ public class IOTDBGroupByIT {
         expectedHeader,
         retArray);
   }
+
+  @Test
+  public void endTimeGroupByTimeTest8() {
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "Device",
+          END_TIMESTAMP_STR,
+          count("temperature"),
+          sum("temperature"),
+          avg("temperature")
+        };
+    String[] retArray =
+        new String[] {
+          "15,root.ln.wf01.wt01,19,0,null,null,",
+          "20,root.ln.wf01.wt01,24,1,20.2,20.2,",
+          "25,root.ln.wf01.wt01,29,0,null,null,",
+          "30,root.ln.wf01.wt01,34,1,30.3,30.3,",
+          "35,root.ln.wf01.wt01,39,0,null,null,"
+        };
+    resultSetEqualTest(
+        "select __endTime,count(temperature), sum(temperature), 
avg(temperature) from "
+            + "root.ln.wf01.wt01 "
+            + "WHERE temperature > 0 "
+            + "GROUP BY ([0, 600), 5ms) "
+            + "limit 5 offset 3 align by device",
+        expectedHeader,
+        retArray);
+  }
+
+  @Test
+  public void endTimeGroupByTimeTest9() {
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "Device",
+          END_TIMESTAMP_STR,
+          count("temperature"),
+          sum("temperature"),
+          avg("temperature")
+        };
+    String[] retArray =
+        new String[] {
+          "45,root.ln.wf01.wt01,45,3,90.9,30.3,",
+          "50,root.ln.wf01.wt01,50,3,121.2,40.4,",
+          "55,root.ln.wf01.wt01,55,3,121.2,40.4,",
+          "60,root.ln.wf01.wt01,60,2,90.9,45.45,",
+          "65,root.ln.wf01.wt01,65,2,90.9,45.45,"
+        };
+    resultSetEqualTest(
+        "select __endTime,count(temperature), sum(temperature), 
avg(temperature) from "
+            + "root.ln.wf01.wt01 "
+            + "GROUP BY ((0, 600], 30ms, 5ms) "
+            + "limit 5 offset 3 align by device",
+        expectedHeader,
+        retArray);
+  }
 }
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthIT.java
index dc33dd9574a..e08eb4c5f1d 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthIT.java
@@ -171,6 +171,20 @@ public class IoTDBGroupByNaturalMonthIT {
         currPrecision);
   }
 
+  @Test
+  public void groupByNaturalMonthTest5() {
+    String[] expectedHeader = new String[] {TIMESTAMP_STR, 
sum("root.sg1.d1.temperature")};
+    String[] retArray = {
+      "01/30/2021:00:00:00,29.0,", "02/28/2021:00:00:00,30.0,", 
"03/30/2021:00:00:00,1.0,"
+    };
+    resultSetEqualTest(
+        "select sum(temperature) from root.sg1.d1 GROUP BY ([2021-01-30, 
2021-03-31), 1mo)",
+        expectedHeader,
+        retArray,
+        df,
+        currPrecision);
+  }
+
   /** Test group by month with order by time desc. */
   @Test
   public void groupByNaturalMonthFailTest() {
@@ -241,12 +255,12 @@ public class IoTDBGroupByNaturalMonthIT {
       "10/31/2020:00:00:00,30.0,",
       "11/10/2020:00:00:00,30.0,",
       "11/20/2020:00:00:00,30.0,",
-      "11/30/2020:00:00:00,31.0,",
-      "12/10/2020:00:00:00,31.0,",
-      "12/20/2020:00:00:00,31.0,",
-      "12/30/2020:00:00:00,31.0,",
-      "01/09/2021:00:00:00,31.0,",
-      "01/19/2021:00:00:00,31.0,",
+      "11/30/2020:00:00:00,30.0,",
+      "12/10/2020:00:00:00,30.0,",
+      "12/20/2020:00:00:00,30.0,",
+      "12/30/2020:00:00:00,30.0,",
+      "01/09/2021:00:00:00,30.0,",
+      "01/19/2021:00:00:00,30.0,",
       "01/29/2021:00:00:00,30.0,",
       "02/08/2021:00:00:00,21.0,",
       "02/18/2021:00:00:00,11.0,",
@@ -363,11 +377,11 @@ public class IoTDBGroupByNaturalMonthIT {
           // [01-28, 03-01)
           "01/28/2023:00:00:00,1,",
           // [03-01, 04-02)
-          "03/01/2023:00:00:00,2,",
+          "03/01/2023:00:00:00,1,",
           // [04-02, 05-03)
-          "04/02/2023:00:00:00,1,",
+          "03/30/2023:00:00:00,1,",
           // [05-03, 05-29)
-          "05/03/2023:00:00:00,0,"
+          "05/01/2023:00:00:00,1,"
         };
     resultSetEqualTest(
         "select count(s1) from root.test.d1 " + "group by ([2023-01-28, 
2023-05-29), 1mo1d)",
@@ -384,12 +398,12 @@ public class IoTDBGroupByNaturalMonthIT {
         new String[] {
           // [01-28, 03-01)
           "1674864000000,1,",
-          // [03-01, 04-02)
-          "1677628800000,2,",
-          // [04-02, 05-03)
-          "1680393600000,1,",
-          // [05-03, 05-29)
-          "1683072000000,0,"
+          // [03-01, 03-30)
+          "1677628800000,1,",
+          // [03-30, 05-01)
+          "1680134400000,1,",
+          // [05-01, 05-29)
+          "1682899200000,1,"
         };
     // the part in timeDuration finer than current time precision will be 
discarded
     resultSetEqualTest(
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthNsPrecisionIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthNsPrecisionIT.java
index 8ff16207304..807ecf6620e 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthNsPrecisionIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthNsPrecisionIT.java
@@ -89,12 +89,12 @@ public class IoTDBGroupByNaturalMonthNsPrecisionIT extends 
IoTDBGroupByNaturalMo
         new String[] {
           // [01-28, 03-01 + 1ns)
           "1674864000000000000,2,",
-          // [03-01 + 1ns, 04-02 + 2ns)
-          "1677628800000000001,1,",
-          // [04-02 + 2ns, 05-03 + 3ns)
-          "1680393600000000002,1,",
-          // [05-03 + 3ns, 05-29 + 4ns)
-          "1683072000000000003,0,"
+          // [03-01 + 1ns, 03-30 + 2ns)
+          "1677628800000000001,0,",
+          // [03-30 + 2ns, 05-01 + 3ns)
+          "1680134400000000002,2,",
+          // [05-01 + 3ns, 05-29 + 4ns)
+          "1682899200000000003,0,"
         };
     // the part in timeDuration finer than current time precision will be 
discarded
     resultSetEqualTest(
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthUsPrecisionIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthUsPrecisionIT.java
index 0f55af25da8..eb4bfec938f 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthUsPrecisionIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBGroupByNaturalMonthUsPrecisionIT.java
@@ -74,12 +74,12 @@ public class IoTDBGroupByNaturalMonthUsPrecisionIT extends 
IoTDBGroupByNaturalMo
         new String[] {
           // [01-28, 03-01)
           "1674864000000000,1,",
-          // [03-01, 04-02)
-          "1677628800000000,2,",
-          // [04-02, 05-03)
-          "1680393600000000,1,",
-          // [05-03, 05-29)
-          "1683072000000000,0,"
+          // [03-01, 03-30)
+          "1677628800000000,1,",
+          // [03-30, 05-01)
+          "1680134400000000,1,",
+          // [05-01, 05-29)
+          "1682899200000000,1,"
         };
     // the part in timeDuration finer than current time precision will be 
discarded
     resultSetEqualTest(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
index eafec00cd8d..3b1a4dc5e71 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
@@ -296,7 +296,7 @@ public class SessionManager implements SessionManagerMBean {
       return session.getTimeZone();
     } else {
       // only used for test
-      return TimeZone.getTimeZone("+08:00");
+      return TimeZone.getTimeZone(ZoneId.systemDefault());
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/aggregation/timerangeiterator/AggrWindowIterator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/aggregation/timerangeiterator/AggrWindowIterator.java
index 25264d58e4f..f370815cb6e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/aggregation/timerangeiterator/AggrWindowIterator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/aggregation/timerangeiterator/AggrWindowIterator.java
@@ -42,6 +42,8 @@ public class AggrWindowIterator implements ITimeRangeIterator 
{
 
   private TimeRange curTimeRange;
   private boolean hasCachedTimeRange;
+  // The number of current timeRange, it's used to calculate the cpu when 
there contains month
+  private int timeRangeCount;
 
   @SuppressWarnings("squid:S107")
   public AggrWindowIterator(
@@ -57,6 +59,7 @@ public class AggrWindowIterator implements ITimeRangeIterator 
{
     this.slidingStep = slidingStep;
     this.isAscending = isAscending;
     this.leftCRightO = leftCRightO;
+    this.timeRangeCount = 0;
   }
 
   @Override
@@ -74,7 +77,7 @@ public class AggrWindowIterator implements ITimeRangeIterator 
{
       // calculate interval length by natural month based on startTime
       // ie. startTIme = 1/31, interval = 1mo, curEndTime will be set to 2/29
       retEndTime =
-          Math.min(DateTimeUtils.calcPositiveIntervalByMonth(startTime, 
interval, 1), endTime);
+          Math.min(DateTimeUtils.calcPositiveIntervalByMonth(startTime, 
interval), endTime);
     } else {
       retEndTime = Math.min(startTime + interval.nonMonthDuration, endTime);
     }
@@ -94,12 +97,17 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
                   (double) queryRange
                       / 
(slidingStep.getMaxTotalDuration(TimestampPrecisionUtils.currPrecision)));
       long tempRetStartTime =
-          DateTimeUtils.calcPositiveIntervalByMonth(startTime, slidingStep, 
intervalNum - 1);
+          DateTimeUtils.calcPositiveIntervalByMonth(
+              startTime, slidingStep.multiple(intervalNum - 1));
       retStartTime = tempRetStartTime;
       while (tempRetStartTime < endTime) {
+        intervalNum++;
         retStartTime = tempRetStartTime;
-        tempRetStartTime = 
DateTimeUtils.calcPositiveIntervalByMonth(retStartTime, slidingStep, 1);
+        tempRetStartTime =
+            DateTimeUtils.calcPositiveIntervalByMonth(
+                retStartTime, slidingStep.multiple(intervalNum - 1));
       }
+      intervalNum -= 1;
     } else {
       intervalNum = (long) Math.ceil(queryRange / (double) 
slidingStep.nonMonthDuration);
       retStartTime = slidingStep.nonMonthDuration * (intervalNum - 1) + 
startTime;
@@ -109,7 +117,10 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
       // calculate interval length by natural month based on curStartTime
       // ie. startTIme = 1/31, interval = 1mo, curEndTime will be set to 2/29
       retEndTime =
-          Math.min(DateTimeUtils.calcPositiveIntervalByMonth(retStartTime, 
interval, 1), endTime);
+          Math.min(
+              DateTimeUtils.calcPositiveIntervalByMonth(
+                  startTime, interval.merge(slidingStep.multiple(intervalNum - 
1))),
+              endTime);
     } else {
       retEndTime = Math.min(retStartTime + interval.nonMonthDuration, endTime);
     }
@@ -123,6 +134,7 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
     }
     if (curTimeRange == null) {
       curTimeRange = getFirstTimeRange();
+      timeRangeCount++;
       hasCachedTimeRange = true;
       return true;
     }
@@ -132,7 +144,9 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
     long curStartTime = curTimeRange.getMin();
     if (isAscending) {
       if (slidingStep.containsMonth()) {
-        retStartTime = DateTimeUtils.calcPositiveIntervalByMonth(curStartTime, 
slidingStep, 1);
+        retStartTime =
+            DateTimeUtils.calcPositiveIntervalByMonth(
+                startTime, slidingStep.multiple(timeRangeCount));
       } else {
         retStartTime = curStartTime + slidingStep.nonMonthDuration;
       }
@@ -142,7 +156,9 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
       }
     } else {
       if (slidingStep.containsMonth()) {
-        retStartTime = DateTimeUtils.calcNegativeIntervalByMonth(curStartTime, 
slidingStep);
+        // group by month doesn't support ascending.
+        throw new UnsupportedOperationException(
+            "Ascending is not supported when sliding step contains month.");
       } else {
         retStartTime = curStartTime - slidingStep.nonMonthDuration;
       }
@@ -152,13 +168,16 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
     }
 
     if (interval.containsMonth()) {
-      retEndTime = DateTimeUtils.calcPositiveIntervalByMonth(retStartTime, 
interval, 1);
+      retEndTime =
+          DateTimeUtils.calcPositiveIntervalByMonth(
+              startTime, slidingStep.multiple(timeRangeCount).merge(interval));
     } else {
       retEndTime = retStartTime + interval.nonMonthDuration;
     }
     retEndTime = Math.min(retEndTime, endTime);
     curTimeRange = new TimeRange(retStartTime, retEndTime);
     hasCachedTimeRange = true;
+    timeRangeCount++;
     return true;
   }
 
@@ -193,10 +212,11 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
                   (double) queryRange
                       / 
(slidingStep.getMaxTotalDuration(TimestampPrecisionUtils.currPrecision)));
       long retStartTime =
-          DateTimeUtils.calcPositiveIntervalByMonth(startTime, slidingStep, 
intervalNum);
+          DateTimeUtils.calcPositiveIntervalByMonth(startTime, 
slidingStep.multiple(intervalNum));
       while (retStartTime < endTime) {
         intervalNum++;
-        retStartTime = DateTimeUtils.calcPositiveIntervalByMonth(retStartTime, 
slidingStep, 1);
+        retStartTime =
+            DateTimeUtils.calcPositiveIntervalByMonth(startTime, 
slidingStep.multiple(intervalNum));
       }
     } else {
       intervalNum = (long) Math.ceil(queryRange / (double) 
slidingStep.nonMonthDuration);
@@ -207,5 +227,6 @@ public class AggrWindowIterator implements 
ITimeRangeIterator {
   public void reset() {
     curTimeRange = null;
     hasCachedTimeRange = false;
+    timeRangeCount = 0;
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDown.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDown.java
index b5081523108..52f7e53fbcb 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDown.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDown.java
@@ -41,10 +41,12 @@ import 
org.apache.iotdb.db.queryengine.plan.statement.component.FillPolicy;
 import 
org.apache.iotdb.db.queryengine.plan.statement.component.GroupByTimeComponent;
 import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
 import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement;
+import org.apache.iotdb.db.utils.DateTimeUtils;
+import org.apache.iotdb.tsfile.utils.TimeDuration;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * <b>Optimization phase:</b> Distributed plan planning
@@ -261,8 +263,61 @@ public class LimitOffsetPushDown implements PlanOptimizer {
     return false;
   }
 
+  private static void pushDownLimitOffsetToTimeParameterContainingMonth(
+      QueryStatement queryStatement) {
+    GroupByTimeComponent groupByTimeComponent = 
queryStatement.getGroupByTimeComponent();
+    long startTime = groupByTimeComponent.getStartTime();
+    long endTime = groupByTimeComponent.getEndTime();
+    TimeDuration slidingStep = groupByTimeComponent.getSlidingStep();
+    TimeDuration interval = groupByTimeComponent.getInterval();
+    long limitSize = queryStatement.getRowLimit();
+    long offsetSize = queryStatement.getRowOffset();
+
+    // Evaluate the day of month as 28 days
+    long totalStep = slidingStep.getMinTotalDuration(TimeUnit.MILLISECONDS);
+    long size = (endTime - startTime + totalStep - 1) / totalStep;
+    if (size > offsetSize) {
+      // ordering in group by month must be ascending
+      long newStartTime =
+          DateTimeUtils.calcPositiveIntervalByMonth(startTime, 
slidingStep.multiple(offsetSize));
+
+      if (limitSize != 0) {
+        endTime =
+            Math.min(
+                endTime,
+                DateTimeUtils.calcPositiveIntervalByMonth(
+                    startTime,
+                    calculateEndTimeDuration(slidingStep, interval, limitSize, 
offsetSize)));
+      }
+      groupByTimeComponent.setEndTime(endTime);
+      groupByTimeComponent.setStartTime(newStartTime);
+    } else {
+      // finish the query, resultSet is empty
+      queryStatement.setResultSetEmpty(true);
+    }
+    // If windows overlap, we need to keep LIMIT because the window size can 
be less than interval
+    // which may result in more windows than we need in the target time range.
+    queryStatement.setRowLimit(interval.isGreaterThan(slidingStep) ? limitSize 
: 0);
+    queryStatement.setRowOffset(0);
+  }
+
+  private static TimeDuration calculateEndTimeDuration(
+      TimeDuration slidingStep, TimeDuration interval, long limitSize, long 
offsetSize) {
+    long length = offsetSize + limitSize - 1;
+    // startTime + offsetSize * step + (limitSize - 1) * step + interval
+    int monthDuration = (int) (length * slidingStep.monthDuration + 
interval.monthDuration);
+    long nonMonthDuration = length * slidingStep.nonMonthDuration + 
interval.nonMonthDuration;
+    return new TimeDuration(monthDuration, nonMonthDuration);
+  }
+
   public static void pushDownLimitOffsetToTimeParameter(QueryStatement 
queryStatement) {
     GroupByTimeComponent groupByTimeComponent = 
queryStatement.getGroupByTimeComponent();
+    // if group by time contains month, we use another push down limit/offset
+    if (groupByTimeComponent.getInterval().containsMonth()
+        || groupByTimeComponent.getSlidingStep().containsMonth()) {
+      pushDownLimitOffsetToTimeParameterContainingMonth(queryStatement);
+      return;
+    }
     long startTime = groupByTimeComponent.getStartTime();
     long endTime = groupByTimeComponent.getEndTime();
     long step = groupByTimeComponent.getSlidingStep().nonMonthDuration;
@@ -313,7 +368,7 @@ public class LimitOffsetPushDown implements PlanOptimizer {
     GroupByTimeComponent groupByTimeComponent = 
queryStatement.getGroupByTimeComponent();
     if (groupByTimeComponent.getInterval().containsMonth()
         || groupByTimeComponent.getSlidingStep().containsMonth()) {
-      return Collections.emptyList();
+      return deviceNames;
     }
     long startTime = groupByTimeComponent.getStartTime();
     long endTime = groupByTimeComponent.getEndTime();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanBuilder.java
index 597451b6def..047fab98504 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanBuilder.java
@@ -450,9 +450,10 @@ public class LogicalPlanBuilder {
       Set<Expression> aggregationExpressions,
       Set<Expression> sourceTransformExpressions,
       LinkedHashMap<Expression, Set<Expression>> crossGroupByExpressions,
-      List<Integer> deviceViewInputIndexes) {
+      List<Integer> deviceViewInputIndexes,
+      boolean outputEndTime) {
     checkArgument(
-        aggregationExpressions.size() == deviceViewInputIndexes.size(),
+        aggregationExpressions.size() <= deviceViewInputIndexes.size(),
         "Each aggregate should correspond to a column of output.");
 
     boolean needCheckAscending = groupByTimeParameter == null;
@@ -461,7 +462,8 @@ public class LogicalPlanBuilder {
     Map<AggregationDescriptor, Integer> aggregationToIndexMap = new 
HashMap<>();
     Map<PartialPath, List<AggregationDescriptor>> countTimeAggregations = new 
HashMap<>();
 
-    int index = 0;
+    // If need output endTime, the first index is used by __endTime
+    int index = outputEndTime ? 1 : 0;
     for (Expression aggregationExpression : aggregationExpressions) {
       AggregationDescriptor aggregationDescriptor =
           createAggregationDescriptor(
@@ -489,6 +491,9 @@ public class LogicalPlanBuilder {
     if (!curStep.isOutputPartial()) {
       // update measurementIndexes
       deviceViewInputIndexes.clear();
+      if (outputEndTime) {
+        deviceViewInputIndexes.add(1);
+      }
       deviceViewInputIndexes.addAll(
           sourceNodeList.stream()
               .map(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
index 1ebb78ef36f..0b131538942 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java
@@ -352,7 +352,8 @@ public class LogicalPlanVisitor extends 
StatementVisitor<PlanNode, MPPQueryConte
                     aggregationExpressions,
                     sourceTransformExpressions,
                     analysis.getCrossGroupByExpressions(),
-                    deviceViewInputIndexes);
+                    deviceViewInputIndexes,
+                    queryStatement.isOutputEndTime());
       }
 
       if (queryStatement.isGroupByTime() && queryStatement.isOutputEndTime()) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
index 278122486d5..6c48e26445e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
@@ -751,18 +751,8 @@ public class DateTimeUtils {
 
   public static final long MS_TO_MONTH = 30 * 86400_000L;
 
-  public static long calcPositiveIntervalByMonth(
-      long startTime, TimeDuration duration, long times) {
+  public static long calcPositiveIntervalByMonth(long startTime, TimeDuration 
duration) {
     return TimeDuration.calcPositiveIntervalByMonth(
-        startTime,
-        duration,
-        times,
-        SessionManager.getInstance().getSessionTimeZone(),
-        TimestampPrecisionUtils.currPrecision);
-  }
-
-  public static long calcNegativeIntervalByMonth(long startTime, TimeDuration 
duration) {
-    return TimeDuration.calcNegativeIntervalByMonth(
         startTime,
         duration,
         SessionManager.getInstance().getSessionTimeZone(),
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/aggregation/TimeRangeIteratorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/aggregation/TimeRangeIteratorTest.java
index 592110d41fc..6a2db3bdd82 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/aggregation/TimeRangeIteratorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/aggregation/TimeRangeIteratorTest.java
@@ -236,16 +236,16 @@ public class TimeRangeIteratorTest {
       "[ 1604102400000 : 1606694399999 ]",
       "[ 1604966400000 : 1607558399999 ]",
       "[ 1605830400000 : 1608422399999 ]",
-      "[ 1606694400000 : 1609372799999 ]",
-      "[ 1607558400000 : 1610236799999 ]",
-      "[ 1608422400000 : 1611100799999 ]",
-      "[ 1609286400000 : 1611964799999 ]",
-      "[ 1610150400000 : 1612828799999 ]",
-      "[ 1611014400000 : 1613692799999 ]",
+      "[ 1606694400000 : 1609286399999 ]",
+      "[ 1607558400000 : 1610150399999 ]",
+      "[ 1608422400000 : 1611014399999 ]",
+      "[ 1609286400000 : 1611878399999 ]",
+      "[ 1610150400000 : 1612742399999 ]",
+      "[ 1611014400000 : 1613606399999 ]",
       "[ 1611878400000 : 1614470399999 ]",
-      "[ 1612742400000 : 1615161599999 ]",
-      "[ 1613606400000 : 1616025599999 ]",
-      "[ 1614470400000 : 1617148799999 ]",
+      "[ 1612742400000 : 1615334399999 ]",
+      "[ 1613606400000 : 1616198399999 ]",
+      "[ 1614470400000 : 1617062399999 ]",
       "[ 1615334400000 : 1617148799999 ]",
       "[ 1616198400000 : 1617148799999 ]",
       "[ 1617062400000 : 1617148799999 ]"
@@ -257,22 +257,14 @@ public class TimeRangeIteratorTest {
       "[ 1606694400000 : 1607558399999 ]",
       "[ 1607558400000 : 1608422399999 ]",
       "[ 1608422400000 : 1609286399999 ]",
-      "[ 1609286400000 : 1609372799999 ]",
-      "[ 1609372800000 : 1610150399999 ]",
-      "[ 1610150400000 : 1610236799999 ]",
-      "[ 1610236800000 : 1611014399999 ]",
-      "[ 1611014400000 : 1611100799999 ]",
-      "[ 1611100800000 : 1611878399999 ]",
-      "[ 1611878400000 : 1611964799999 ]",
-      "[ 1611964800000 : 1612742399999 ]",
-      "[ 1612742400000 : 1612828799999 ]",
-      "[ 1612828800000 : 1613606399999 ]",
-      "[ 1613606400000 : 1613692799999 ]",
-      "[ 1613692800000 : 1614470399999 ]",
-      "[ 1614470400000 : 1615161599999 ]",
-      "[ 1615161600000 : 1615334399999 ]",
-      "[ 1615334400000 : 1616025599999 ]",
-      "[ 1616025600000 : 1616198399999 ]",
+      "[ 1609286400000 : 1610150399999 ]",
+      "[ 1610150400000 : 1611014399999 ]",
+      "[ 1611014400000 : 1611878399999 ]",
+      "[ 1611878400000 : 1612742399999 ]",
+      "[ 1612742400000 : 1613606399999 ]",
+      "[ 1613606400000 : 1614470399999 ]",
+      "[ 1614470400000 : 1615334399999 ]",
+      "[ 1615334400000 : 1616198399999 ]",
       "[ 1616198400000 : 1617062399999 ]",
       "[ 1617062400000 : 1617148799999 ]"
     };
@@ -350,15 +342,15 @@ public class TimeRangeIteratorTest {
           "[ "
               + Timestamp.valueOf("2023-03-01 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-04-02 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-03-30 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-04-02 00:00:00").getTime()
+              + Timestamp.valueOf("2023-03-30 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-05-03 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-05-01 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-05-03 00:00:00").getTime()
+              + Timestamp.valueOf("2023-05-01 00:00:00").getTime()
               + " : "
               + (Timestamp.valueOf("2023-05-29 00:00:00").getTime() - 1L)
               + " ]"
@@ -389,25 +381,25 @@ public class TimeRangeIteratorTest {
           "[ "
               + Timestamp.valueOf("2023-03-02 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-04-02 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-03-30 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-04-02 00:00:00").getTime()
+              + Timestamp.valueOf("2023-03-30 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-04-03 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-03-31 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-04-03 00:00:00").getTime()
+              + Timestamp.valueOf("2023-03-31 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-05-03 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-05-01 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-05-03 00:00:00").getTime()
+              + Timestamp.valueOf("2023-05-01 00:00:00").getTime()
               + " : "
-              + (Timestamp.valueOf("2023-05-04 00:00:00").getTime() - 1L)
+              + (Timestamp.valueOf("2023-05-02 00:00:00").getTime() - 1L)
               + " ]",
           "[ "
-              + Timestamp.valueOf("2023-05-04 00:00:00").getTime()
+              + Timestamp.valueOf("2023-05-02 00:00:00").getTime()
               + " : "
               + (Timestamp.valueOf("2023-05-29 00:00:00").getTime() - 1L)
               + " ]",
diff --git 
a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/GroupByMonthFilter.java
 
b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/GroupByMonthFilter.java
index 0c2a9895b6e..58cda989643 100644
--- 
a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/GroupByMonthFilter.java
+++ 
b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/GroupByMonthFilter.java
@@ -186,14 +186,19 @@ public class GroupByMonthFilter extends GroupByFilter {
       }
       this.startTime = startTimes[n];
       this.slidingStep =
-          calcPositiveIntervalByMonth(startTime, originalSlidingStep, 1, 
timeZone, currPrecision)
+          calcPositiveIntervalByMonth(
+                  originalStartTime, originalSlidingStep.multiple(n + 1), 
timeZone, currPrecision)
               - startTime;
     } else {
-      startTime = originalStartTime + n * slidingStep;
+      this.startTime = originalStartTime + n * slidingStep;
     }
     if (originalInterval.containsMonth()) {
       this.interval =
-          calcPositiveIntervalByMonth(startTime, originalInterval, 1, 
timeZone, currPrecision)
+          calcPositiveIntervalByMonth(
+                  originalStartTime,
+                  originalSlidingStep.multiple(n).merge(originalInterval),
+                  timeZone,
+                  currPrecision)
               - startTime;
     }
   }
diff --git 
a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TimeDuration.java
 
b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TimeDuration.java
index 9e3d5d20581..8a0045ddf63 100644
--- 
a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TimeDuration.java
+++ 
b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TimeDuration.java
@@ -22,7 +22,9 @@ import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -59,6 +61,24 @@ public class TimeDuration implements Serializable {
         + nonMonthDuration;
   }
 
+  public boolean isGreaterThan(TimeDuration right) {
+    if (this.monthDuration > right.monthDuration) {
+      return true;
+    } else if (this.monthDuration == right.monthDuration) {
+      return this.nonMonthDuration > right.nonMonthDuration;
+    }
+    return false;
+  }
+
+  public TimeDuration merge(TimeDuration other) {
+    return new TimeDuration(
+        this.monthDuration + other.monthDuration, this.nonMonthDuration + 
other.nonMonthDuration);
+  }
+
+  public TimeDuration multiple(long times) {
+    return new TimeDuration((int) (monthDuration * times), nonMonthDuration * 
times);
+  }
+
   /** Think month as 28 days. */
   public long getMinTotalDuration(TimeUnit currPrecision) {
     return currPrecision.convert(monthDuration * 28 * 86400_000L, 
TimeUnit.MILLISECONDS)
@@ -88,76 +108,47 @@ public class TimeDuration implements Serializable {
       TimeUnit currPrecision) {
     long[] result = new long[length];
     result[0] = startTime;
-    Calendar calendar = Calendar.getInstance();
-    calendar.setTimeZone(timeZone);
     for (int i = 1; i < length; i++) {
-      result[i] = getStartTime(result[i - 1], duration, currPrecision, 
calendar);
+      result[i] = getStartTime(startTime, duration.multiple(i), currPrecision, 
timeZone.toZoneId());
     }
     return result;
   }
 
   /**
-   * Add several time durations contains natural months based on the startTime 
and avoid edge cases,
-   * ie 2/28
+   * Add time duration contains natural months to startTime.
+   *
+   * <p>Attention: This method does not support accumulation. If you need to 
calculate the date two
+   * months after the start time, just add two months directly through 
duration, rather than adding
+   * one month first and then adding another month in a loop, it will get 
wrong result.
+   *
+   * <p>There is an example:
+   *
+   * <pre>
+   * 1.30 + 2mo = 3.30(right)
+   * 1.30 + 1mo = 2.28, 2.28 + 1mo = 3.28(wrong)
+   * </pre>
    *
    * @param startTime start time
    * @param duration one duration
-   * @param times num of duration elapsed
    * @return the time after durations elapsed
    */
   public static long calcPositiveIntervalByMonth(
-      long startTime,
-      TimeDuration duration,
-      long times,
-      TimeZone timeZone,
-      TimeUnit currPrecision) {
-    Calendar calendar = Calendar.getInstance();
-    calendar.setTimeZone(timeZone);
-    for (int i = 0; i < times; i++) {
-      startTime = getStartTime(startTime, duration, currPrecision, calendar);
-    }
-    return startTime;
+      long startTime, TimeDuration duration, TimeZone timeZone, TimeUnit 
currPrecision) {
+    return getStartTime(startTime, duration, currPrecision, 
timeZone.toZoneId());
   }
 
   private static long getStartTime(
-      long startTime, TimeDuration duration, TimeUnit currPrecision, Calendar 
calendar) {
+      long startTime, TimeDuration duration, TimeUnit currPrecision, ZoneId 
zoneId) {
     long coarserThanMsPart = getCoarserThanMsPart(startTime, currPrecision);
-    calendar.setTimeInMillis(coarserThanMsPart);
-    boolean isLastDayOfMonth =
-        calendar.get(Calendar.DAY_OF_MONTH) == 
calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
-    calendar.add(Calendar.MONTH, duration.monthDuration);
-    if (isLastDayOfMonth) {
-      calendar.set(Calendar.DAY_OF_MONTH, 
calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
-    }
-    return currPrecision.convert(calendar.getTimeInMillis(), 
TimeUnit.MILLISECONDS)
+    LocalDateTime localDateTime =
+        LocalDateTime.ofInstant(Instant.ofEpochMilli(coarserThanMsPart), 
zoneId);
+    localDateTime = localDateTime.plusMonths(duration.monthDuration);
+    return currPrecision.convert(
+            localDateTime.atZone(zoneId).toInstant().toEpochMilli(), 
TimeUnit.MILLISECONDS)
         + getFinerThanMsPart(startTime, currPrecision)
         + duration.nonMonthDuration;
   }
 
-  /**
-   * subtract time duration contains natural months based on the startTime
-   *
-   * @param startTime start time
-   * @param duration the duration
-   * @return the time before duration
-   */
-  public static long calcNegativeIntervalByMonth(
-      long startTime, TimeDuration duration, TimeZone timeZone, TimeUnit 
currPrecision) {
-    Calendar calendar = Calendar.getInstance();
-    calendar.setTimeZone(timeZone);
-    long timeBeforeMonthElapsedInMs =
-        TimeUnit.MILLISECONDS.convert(startTime - duration.nonMonthDuration, 
currPrecision);
-    calendar.setTimeInMillis(timeBeforeMonthElapsedInMs);
-    boolean isLastDayOfMonth =
-        calendar.get(Calendar.DAY_OF_MONTH) == 
calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
-    calendar.add(Calendar.MONTH, -duration.monthDuration);
-    if (isLastDayOfMonth) {
-      calendar.set(Calendar.DAY_OF_MONTH, 
calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
-    }
-    return currPrecision.convert(calendar.getTimeInMillis(), 
TimeUnit.MILLISECONDS)
-        + getFinerThanMsPart(startTime - duration.nonMonthDuration, 
currPrecision);
-  }
-
   private static long getCoarserThanMsPart(long time, TimeUnit currPrecision) {
     return TimeUnit.MILLISECONDS.convert(time, currPrecision);
   }
diff --git 
a/iotdb-core/tsfile/src/test/java/org/apache/iotdb/tsfile/utils/TimeDurationTest.java
 
b/iotdb-core/tsfile/src/test/java/org/apache/iotdb/tsfile/utils/TimeDurationTest.java
index ef3154f9590..ce8d7c9acc9 100644
--- 
a/iotdb-core/tsfile/src/test/java/org/apache/iotdb/tsfile/utils/TimeDurationTest.java
+++ 
b/iotdb-core/tsfile/src/test/java/org/apache/iotdb/tsfile/utils/TimeDurationTest.java
@@ -25,43 +25,49 @@ import java.sql.Timestamp;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 
-import static 
org.apache.iotdb.tsfile.utils.TimeDuration.calcNegativeIntervalByMonth;
 import static 
org.apache.iotdb.tsfile.utils.TimeDuration.calcPositiveIntervalByMonth;
 
 public class TimeDurationTest {
   @Test
   public void calculateIntervalTest() {
-    // 1mo1d duration after 2023-01-31
+    // 1mo duration after 2023-01-31
     long result =
+        calcPositiveIntervalByMonth(
+            Timestamp.valueOf("2023-01-31 00:00:00").getTime(),
+            new TimeDuration(1, 0),
+            TimeZone.getDefault(),
+            TimeUnit.MILLISECONDS);
+    Assert.assertEquals(Timestamp.valueOf("2023-02-28 00:00:00").getTime(), 
result);
+    result =
+        calcPositiveIntervalByMonth(
+            Timestamp.valueOf("2023-02-28 00:00:00").getTime(),
+            new TimeDuration(2, 0),
+            TimeZone.getDefault(),
+            TimeUnit.MILLISECONDS);
+    Assert.assertEquals(Timestamp.valueOf("2023-04-28 00:00:00").getTime(), 
result);
+    result =
+        calcPositiveIntervalByMonth(
+            Timestamp.valueOf("2023-01-30 00:00:00").getTime(),
+            new TimeDuration(2, 0),
+            TimeZone.getDefault(),
+            TimeUnit.MILLISECONDS);
+    Assert.assertEquals(Timestamp.valueOf("2023-03-30 00:00:00").getTime(), 
result);
+    // 1mo1d duration after 2023-01-31
+    result =
         calcPositiveIntervalByMonth(
             Timestamp.valueOf("2023-01-31 00:00:00").getTime(),
             new TimeDuration(1, 86400_000),
-            1,
             TimeZone.getDefault(),
             TimeUnit.MILLISECONDS);
     Assert.assertEquals(Timestamp.valueOf("2023-03-01 00:00:00").getTime(), 
result);
-    // 1mo1d duration before 2023-03-01
-    result =
-        calcNegativeIntervalByMonth(
-            result, new TimeDuration(1, 86400_000), TimeZone.getDefault(), 
TimeUnit.MILLISECONDS);
-    Assert.assertEquals(Timestamp.valueOf("2023-01-31 00:00:00").getTime(), 
result);
 
     // 1mo1d1ns duration after 2023-01-31
     result =
         calcPositiveIntervalByMonth(
             Timestamp.valueOf("2023-01-31 00:00:00").getTime() * 1000_000,
             new TimeDuration(1, 86400_000_000_001L),
-            1,
             TimeZone.getDefault(),
             TimeUnit.NANOSECONDS);
     Assert.assertEquals(Timestamp.valueOf("2023-03-01 00:00:00").getTime() * 
1000_000 + 1, result);
-    // 1mo1d1ns duration before 2023-03-01
-    result =
-        calcNegativeIntervalByMonth(
-            result,
-            new TimeDuration(1, 86400_000_000_001L),
-            TimeZone.getDefault(),
-            TimeUnit.NANOSECONDS);
-    Assert.assertEquals(Timestamp.valueOf("2023-01-31 00:00:00").getTime() * 
1000_000, result);
   }
 }


Reply via email to