This is an automated email from the ASF dual-hosted git repository.

hxd 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 04be7b6  Fix group by month bug that use calendar month rather than 
use 30 days (#2964)
04be7b6 is described below

commit 04be7b612019b0b426cf9a3140031859d271a2d5
Author: Xiangwei Wei <[email protected]>
AuthorDate: Mon Apr 5 11:28:20 2021 +0800

    Fix group by month bug that use calendar month rather than use 30 days 
(#2964)
    
    * Seperate IOTDBGroupByMonthIT from IoTDBGroupByIT
---
 .../ClusterGroupByNoVFilterDataSetTest.java        |   3 +
 .../groupby/GroupByWithoutValueFilterDataSet.java  |   5 +-
 .../iotdb/db/query/executor/QueryRouter.java       |  27 ++-
 .../iotdb/db/integration/IOTDBGroupByIT.java       | 173 ++--------------
 .../iotdb/db/integration/IoTDBGroupByMonthIT.java  | 180 ++++++++++++++++
 .../iotdb/tsfile/read/filter/GroupByFilter.java    |  11 +-
 .../tsfile/read/filter/GroupByMonthFilter.java     | 176 ++++++++++++++++
 .../tsfile/read/filter/GroupByMonthFilterTest.java | 226 +++++++++++++++++++++
 8 files changed, 627 insertions(+), 174 deletions(-)

diff --git 
a/cluster/src/test/java/org/apache/iotdb/cluster/query/groupby/ClusterGroupByNoVFilterDataSetTest.java
 
b/cluster/src/test/java/org/apache/iotdb/cluster/query/groupby/ClusterGroupByNoVFilterDataSetTest.java
index 3b95ed4..66aa6e5 100644
--- 
a/cluster/src/test/java/org/apache/iotdb/cluster/query/groupby/ClusterGroupByNoVFilterDataSetTest.java
+++ 
b/cluster/src/test/java/org/apache/iotdb/cluster/query/groupby/ClusterGroupByNoVFilterDataSetTest.java
@@ -31,6 +31,8 @@ import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
 import org.apache.iotdb.db.query.context.QueryContext;
 import org.apache.iotdb.db.query.control.QueryResourceManager;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression;
+import org.apache.iotdb.tsfile.read.filter.GroupByFilter;
 
 import org.junit.Test;
 
@@ -68,6 +70,7 @@ public class ClusterGroupByNoVFilterDataSetTest extends 
BaseQueryTest {
       groupByPlan.setEndTime(20);
       groupByPlan.setSlidingStep(5);
       groupByPlan.setInterval(5);
+      groupByPlan.setExpression(new GlobalTimeExpression(new GroupByFilter(5, 
5, 0, 20)));
 
       ClusterGroupByNoVFilterDataSet dataSet =
           new ClusterGroupByNoVFilterDataSet(queryContext, groupByPlan, 
testMetaMember);
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
 
b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
index 136fa60..0c2025f 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
@@ -85,6 +85,9 @@ public class GroupByWithoutValueFilterDataSet extends 
GroupByEngineDataSet {
     if (expression != null) {
       timeFilter = ((GlobalTimeExpression) expression).getFilter();
     }
+    if (timeFilter == null) {
+      throw new QueryProcessException("TimeFilter cannot be null in GroupBy 
query.");
+    }
 
     List<StorageGroupProcessor> list =
         StorageEngine.getInstance()
@@ -102,7 +105,7 @@ public class GroupByWithoutValueFilterDataSet extends 
GroupByEngineDataSet {
                   groupByTimePlan.getAllMeasurementsInDevice(path.getDevice()),
                   dataTypes.get(i),
                   context,
-                  timeFilter,
+                  timeFilter.copy(),
                   null,
                   groupByTimePlan.isAscending()));
           resultIndexes.put(path, new ArrayList<>());
diff --git 
a/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java 
b/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
index 3478c6c..287856e 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
@@ -44,6 +44,7 @@ import 
org.apache.iotdb.tsfile.read.expression.impl.BinaryExpression;
 import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression;
 import org.apache.iotdb.tsfile.read.expression.util.ExpressionOptimizer;
 import org.apache.iotdb.tsfile.read.filter.GroupByFilter;
+import org.apache.iotdb.tsfile.read.filter.GroupByMonthFilter;
 import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
 
 import org.slf4j.Logger;
@@ -154,16 +155,9 @@ public class QueryRouter implements IQueryRouter {
     }
 
     GroupByEngineDataSet dataSet = null;
-    long unit = groupByTimePlan.getInterval();
-    long slidingStep = groupByTimePlan.getSlidingStep();
-    long startTime = groupByTimePlan.getStartTime();
-    long endTime = groupByTimePlan.getEndTime();
-
     IExpression expression = groupByTimePlan.getExpression();
     List<PartialPath> selectedSeries = groupByTimePlan.getDeduplicatedPaths();
-
-    GlobalTimeExpression timeExpression =
-        new GlobalTimeExpression(new GroupByFilter(unit, slidingStep, 
startTime, endTime));
+    GlobalTimeExpression timeExpression = getTimeExpression(groupByTimePlan);
 
     if (expression == null) {
       expression = timeExpression;
@@ -191,6 +185,23 @@ public class QueryRouter implements IQueryRouter {
     return dataSet;
   }
 
+  private GlobalTimeExpression getTimeExpression(GroupByTimePlan plan) {
+    if (plan.isSlidingStepByMonth() || plan.isIntervalByMonth()) {
+      return new GlobalTimeExpression(
+          (new GroupByMonthFilter(
+              plan.getInterval(),
+              plan.getSlidingStep(),
+              plan.getStartTime(),
+              plan.getEndTime(),
+              plan.isSlidingStepByMonth(),
+              plan.isIntervalByMonth())));
+    } else {
+      return new GlobalTimeExpression(
+          new GroupByFilter(
+              plan.getInterval(), plan.getSlidingStep(), plan.getStartTime(), 
plan.getEndTime()));
+    }
+  }
+
   protected GroupByWithoutValueFilterDataSet 
getGroupByWithoutValueFilterDataSet(
       QueryContext context, GroupByTimePlan plan)
       throws StorageEngineException, QueryProcessException {
diff --git 
a/server/src/test/java/org/apache/iotdb/db/integration/IOTDBGroupByIT.java 
b/server/src/test/java/org/apache/iotdb/db/integration/IOTDBGroupByIT.java
index 04a04ba..3d16ddf 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IOTDBGroupByIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IOTDBGroupByIT.java
@@ -29,14 +29,20 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.sql.*;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TimeZone;
-
-import static org.apache.iotdb.db.constant.TestConstant.*;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import static org.apache.iotdb.db.constant.TestConstant.avg;
+import static org.apache.iotdb.db.constant.TestConstant.count;
+import static org.apache.iotdb.db.constant.TestConstant.first_value;
+import static org.apache.iotdb.db.constant.TestConstant.last_value;
+import static org.apache.iotdb.db.constant.TestConstant.max_time;
+import static org.apache.iotdb.db.constant.TestConstant.max_value;
+import static org.apache.iotdb.db.constant.TestConstant.min_time;
+import static org.apache.iotdb.db.constant.TestConstant.min_value;
+import static org.apache.iotdb.db.constant.TestConstant.sum;
 import static org.junit.Assert.fail;
 
 public class IOTDBGroupByIT {
@@ -900,157 +906,6 @@ public class IOTDBGroupByIT {
   }
 
   @Test
-  public void groupByNaturalMonth1() {
-    try (Connection connection =
-            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
-        Statement statement = connection.createStatement()) {
-
-      // 10/31/2019:19:57:18
-      long startTime = 1572523038000L;
-      // 04/01/2020:19:57:18
-      long endTime = 1585742238000L;
-
-      String[] retArray1 = {
-        "10/31/2019:19:57:18",
-        "30.0",
-        "11/30/2019:19:57:18",
-        "31.0",
-        "12/31/2019:19:57:18",
-        "31.0",
-        "01/31/2020:19:57:18",
-        "29.0",
-        "02/29/2020:19:57:18",
-        "31.0",
-        "03/31/2020:19:57:18",
-        "1.0"
-      };
-
-      for (long i = startTime; i <= endTime; i += 86400_000L) {
-        statement.execute("insert into root.sg1.d1(timestamp, temperature) 
values (" + i + ", 1)");
-      }
-
-      DateFormat df = new SimpleDateFormat("MM/dd/yyyy:HH:mm:ss");
-      df.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
-
-      boolean hasResultSet =
-          statement.execute(
-              "select sum(temperature) from "
-                  + "root.sg1.d1 "
-                  + "GROUP BY ([1572523038000, 1585742238000), 1mo, 1mo)");
-
-      Assert.assertTrue(hasResultSet);
-      int cnt;
-      try (ResultSet resultSet = statement.getResultSet()) {
-        cnt = 0;
-        while (resultSet.next()) {
-          String time = resultSet.getString(TIMESTAMP_STR);
-          String ans = resultSet.getString(sum("root.sg1.d1.temperature"));
-          Assert.assertEquals(retArray1[cnt++], 
df.format(Long.parseLong(time)));
-          Assert.assertEquals(retArray1[cnt++], ans);
-        }
-        Assert.assertEquals(retArray1.length, cnt);
-      }
-    } catch (Exception e) {
-      e.printStackTrace();
-      fail(e.getMessage());
-    }
-  }
-
-  @Test
-  public void groupByNaturalMonth2() {
-    try (Connection connection =
-            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
-        Statement statement = connection.createStatement()) {
-
-      // 10/31/2019:19:57:18
-      long startTime = 1572523038000L;
-      // 04/01/2020:19:57:18
-      long endTime = 1585742238000L;
-
-      String[] retArray1 = {
-        "10/31/2019:19:57:18",
-        "10.0",
-        "11/30/2019:19:57:18",
-        "10.0",
-        "12/31/2019:19:57:18",
-        "9.0",
-        "01/31/2020:19:57:18",
-        "8.0",
-        "02/29/2020:19:57:18",
-        "9.0",
-        "03/31/2020:19:57:18",
-        "1.0"
-      };
-      List<String> start = new ArrayList<>();
-
-      for (long i = startTime; i <= endTime; i += 86400_000L) {
-        statement.execute("insert into root.sg1.d1(timestamp, temperature) 
values (" + i + ", 1)");
-      }
-
-      DateFormat df = new SimpleDateFormat("MM/dd/yyyy:HH:mm:ss");
-      df.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
-
-      boolean hasResultSet =
-          statement.execute(
-              "select sum(temperature) from "
-                  + "root.sg1.d1 "
-                  + "GROUP BY ([1572523038000, 1585742238000), 10d, 1mo)");
-
-      Assert.assertTrue(hasResultSet);
-      int cnt;
-      try (ResultSet resultSet = statement.getResultSet()) {
-        cnt = 0;
-        while (resultSet.next()) {
-          String time = resultSet.getString(TIMESTAMP_STR);
-          String ans = resultSet.getString(sum("root.sg1.d1.temperature"));
-          Assert.assertEquals(retArray1[cnt++], 
df.format(Long.parseLong(time)));
-          Assert.assertEquals(retArray1[cnt++], ans);
-        }
-        Assert.assertEquals(retArray1.length, cnt);
-      }
-    } catch (Exception e) {
-      e.printStackTrace();
-      fail(e.getMessage());
-    }
-  }
-
-  @Test
-  public void groupByNaturalMonth3() {
-    // test when endtime - starttime = interval
-    try (Connection connection =
-            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
-        Statement statement = connection.createStatement()) {
-
-      // 2/1/2021:15:20
-      long startTime = 1612164041000L;
-      // 3/1/2021:15:20
-      long endTime = 1614583241000L;
-
-      for (long i = startTime; i <= endTime; i += 86400_000L) {
-        statement.execute("insert into root.sg1.d1(timestamp, temperature) 
values (" + i + ", 1)");
-      }
-
-      DateFormat df = new SimpleDateFormat("MM/dd/yyyy:HH:mm:ss");
-      df.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
-
-      boolean hasResultSet =
-          statement.execute(
-              "select sum(temperature) from root.sg1.d1 "
-                  + "GROUP BY ([1612164041000, 1614583241000), 1mo)");
-
-      Assert.assertTrue(hasResultSet);
-      int cnt = 0;
-      try (ResultSet resultSet = statement.getResultSet()) {
-        cnt++;
-      }
-      Assert.assertEquals(1, cnt);
-    } catch (Exception e) {
-      e.printStackTrace();
-      fail(e.getMessage());
-    }
-  }
-
-  @Test
   public void usingNowFunction() {
     System.out.println("usingNowFunction");
     try (Connection connection =
diff --git 
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByMonthIT.java 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByMonthIT.java
new file mode 100644
index 0000000..b2352f4
--- /dev/null
+++ 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByMonthIT.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.integration;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.engine.compaction.CompactionStrategy;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+import static org.apache.iotdb.db.constant.TestConstant.sum;
+import static org.junit.Assert.fail;
+
+public class IoTDBGroupByMonthIT {
+
+  private static final String TIMESTAMP_STR = "Time";
+  private final DateFormat df = new SimpleDateFormat("MM/dd/yyyy:HH:mm:ss");
+
+  @Before
+  public void setUp() throws Exception {
+    IoTDBDescriptor.getInstance()
+        .getConfig()
+        .setCompactionStrategy(CompactionStrategy.NO_COMPACTION);
+    df.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
+    EnvironmentUtils.envSetUp();
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    prepareData();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    EnvironmentUtils.cleanEnv();
+    IoTDBDescriptor.getInstance()
+        .getConfig()
+        .setCompactionStrategy(CompactionStrategy.LEVEL_COMPACTION);
+  }
+
+  @Test
+  public void groupByNaturalMonth1() {
+    try (Connection connection =
+            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+        Statement statement = connection.createStatement()) {
+
+      String[] retArray1 = {
+        "10/31/2020:00:00:00", "30.0",
+        "11/30/2020:00:00:00", "31.0",
+        "12/31/2020:00:00:00", "31.0",
+        "01/31/2021:00:00:00", "28.0",
+        "02/28/2021:00:00:00", "1.0"
+      };
+
+      boolean hasResultSet =
+          statement.execute(
+              "select sum(temperature) from root.sg1.d1 "
+                  + "GROUP BY ([1604102400000, 1614556800000), 1mo, 1mo)");
+
+      Assert.assertTrue(hasResultSet);
+      int cnt = 0;
+      try (ResultSet resultSet = statement.getResultSet()) {
+        while (resultSet.next()) {
+          String time = resultSet.getString(TIMESTAMP_STR);
+          String ans = resultSet.getString(sum("root.sg1.d1.temperature"));
+          Assert.assertEquals(retArray1[cnt++], 
df.format(Long.parseLong(time)));
+          Assert.assertEquals(retArray1[cnt++], ans);
+        }
+        Assert.assertEquals(retArray1.length, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void groupByNaturalMonth2() {
+    try (Connection connection =
+            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+        Statement statement = connection.createStatement()) {
+
+      String[] retArray1 = {
+        "10/31/2020:00:00:00", "10.0",
+        "11/30/2020:00:00:00", "10.0",
+        "12/31/2020:00:00:00", "10.0",
+        "01/31/2021:00:00:00", "10.0",
+        "02/28/2021:00:00:00", "1.0"
+      };
+
+      boolean hasResultSet =
+          statement.execute(
+              "select sum(temperature) from root.sg1.d1 "
+                  + "GROUP BY ([1604102400000, 1614556800000), 10d, 1mo)");
+
+      Assert.assertTrue(hasResultSet);
+      int cnt = 0;
+      try (ResultSet resultSet = statement.getResultSet()) {
+        while (resultSet.next()) {
+          String time = resultSet.getString(TIMESTAMP_STR);
+          String ans = resultSet.getString(sum("root.sg1.d1.temperature"));
+          Assert.assertEquals(retArray1[cnt++], 
df.format(Long.parseLong(time)));
+          Assert.assertEquals(retArray1[cnt++], ans);
+        }
+        Assert.assertEquals(retArray1.length, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  /** Test when endTime - startTime = interval */
+  @Test
+  public void groupByNaturalMonth3() {
+    try (Connection connection =
+            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+        Statement statement = connection.createStatement()) {
+
+      boolean hasResultSet =
+          statement.execute(
+              "select sum(temperature) from root.sg1.d1 "
+                  + "GROUP BY ([1604102400000, 1606694400000), 1mo)");
+
+      Assert.assertTrue(hasResultSet);
+      int cnt = 0;
+      ResultSet resultSet = statement.getResultSet();
+      while (resultSet.next()) {
+        cnt++;
+      }
+      Assert.assertEquals(1, cnt);
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private void prepareData() {
+    try (Connection connection =
+            DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+        Statement statement = connection.createStatement()) {
+
+      // 2020-10-31 08:00:00
+      long startTime = 1604102400000L;
+      // 2021-03-01 08:00:00
+      long endTime = 1614556800000L;
+
+      for (long i = startTime; i <= endTime; i += 86400_000L) {
+        statement.execute("insert into root.sg1.d1(timestamp, temperature) 
values (" + i + ", 1)");
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git 
a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByFilter.java 
b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByFilter.java
index 8018e92..d53b450 100644
--- 
a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByFilter.java
+++ 
b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByFilter.java
@@ -31,10 +31,10 @@ import java.util.Objects;
 public class GroupByFilter implements Filter, Serializable {
 
   private static final long serialVersionUID = -1211805021419281440L;
-  private long interval;
-  private long slidingStep;
-  private long startTime;
-  private long endTime;
+  protected long interval;
+  protected long slidingStep;
+  protected long startTime;
+  protected long endTime;
 
   public GroupByFilter(long interval, long slidingStep, long startTime, long 
endTime) {
     this.interval = interval;
@@ -58,9 +58,8 @@ public class GroupByFilter implements Filter, Serializable {
 
   @Override
   public boolean satisfyStartEndTime(long startTime, long endTime) {
-    if (endTime < this.startTime) return false;
+    if (endTime < this.startTime || startTime >= this.endTime) return false;
     else if (startTime <= this.startTime) return true;
-    else if (startTime >= this.endTime) return false;
     else {
       long minTime = startTime - this.startTime;
       long count = minTime / slidingStep;
diff --git 
a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilter.java
 
b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilter.java
new file mode 100644
index 0000000..e86b74e
--- /dev/null
+++ 
b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilter.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.tsfile.read.filter;
+
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+
+import java.util.Calendar;
+import java.util.Objects;
+
+/**
+ * GroupByMonthFilter is used to handle natural month slidingStep and interval 
by generating
+ * dynamically. Attention: it's only supported to access in ascending order 
now.
+ */
+public class GroupByMonthFilter extends GroupByFilter {
+
+  private final boolean isSlidingStepByMonth;
+  private final boolean isIntervalByMonth;
+  private int slidingStepsInMo;
+  private int intervalInMo;
+  private final Calendar calendar = Calendar.getInstance();
+  private static final long MS_TO_MONTH = 30 * 86400_000L;
+  private int intervalCnt = 0;
+  /** 10.31 -> 11.30 -> 12.31, not 10.31 -> 11.30 -> 12.30 */
+  private final long initialStartTime;
+
+  public GroupByMonthFilter(
+      long interval,
+      long slidingStep,
+      long startTime,
+      long endTime,
+      boolean isSlidingStepByMonth,
+      boolean isIntervalByMonth) {
+    super(interval, slidingStep, startTime, endTime);
+    initialStartTime = startTime;
+    calendar.setTimeInMillis(startTime);
+    this.isIntervalByMonth = isIntervalByMonth;
+    this.isSlidingStepByMonth = isSlidingStepByMonth;
+    if (isIntervalByMonth) {
+      // TODO: 1mo1d
+      intervalInMo = (int) (interval / MS_TO_MONTH);
+    }
+    if (isSlidingStepByMonth) {
+      slidingStepsInMo = (int) (slidingStep / MS_TO_MONTH);
+    }
+    getNextIntervalAndSlidingStep();
+  }
+
+  // TODO: time descending order
+  @Override
+  public boolean satisfy(long time, Object value) {
+    if (time < startTime || time >= endTime) {
+      return false;
+    } else if (time - startTime < interval) {
+      return true;
+    } else {
+      this.startTime = calendar.getTimeInMillis();
+      getNextIntervalAndSlidingStep();
+      return satisfy(time, value);
+    }
+  }
+
+  @Override
+  public boolean satisfyStartEndTime(long startTime, long endTime) {
+    boolean isSatisfy = satisfyCurrentInterval(startTime, endTime);
+    if (isSatisfy) {
+      return true;
+    } else {
+      long beforeStartTime = this.startTime;
+      int beforeIntervalCnt = this.intervalCnt;
+      // TODO: optimize to jump but not one by one
+      while (endTime >= this.startTime && !isSatisfy) {
+        this.startTime = calendar.getTimeInMillis();
+        getNextIntervalAndSlidingStep();
+        isSatisfy = satisfyCurrentInterval(startTime, endTime);
+      }
+      // recover the initial state
+      this.intervalCnt = beforeIntervalCnt - 1;
+      this.startTime = beforeStartTime;
+      getNextIntervalAndSlidingStep();
+      return isSatisfy;
+    }
+  }
+
+  @Override
+  public Filter copy() {
+    return new GroupByMonthFilter(
+        interval, slidingStep, startTime, endTime, isSlidingStepByMonth, 
isIntervalByMonth);
+  }
+
+  private boolean satisfyCurrentInterval(long startTime, long endTime) {
+    if (endTime < this.startTime || startTime >= this.endTime) {
+      return false;
+    } else {
+      return startTime <= this.startTime || startTime - this.startTime < 
interval;
+    }
+  }
+
+  @Override
+  public boolean containStartEndTime(long startTime, long endTime) {
+    boolean isContained = isContainedByCurrentInterval(startTime, endTime);
+    if (isContained) {
+      return true;
+    } else {
+      long beforeStartTime = this.startTime;
+      int beforeIntervalCnt = this.intervalCnt;
+      while (!isContained && startTime >= this.startTime) {
+        this.startTime = calendar.getTimeInMillis();
+        getNextIntervalAndSlidingStep();
+        isContained = isContainedByCurrentInterval(startTime, endTime);
+      }
+      // recover the initial state
+      this.intervalCnt = beforeIntervalCnt - 1;
+      this.startTime = beforeStartTime;
+      getNextIntervalAndSlidingStep();
+      return isContained;
+    }
+  }
+
+  private boolean isContainedByCurrentInterval(long startTime, long endTime) {
+    if (startTime < this.startTime || endTime > this.endTime) {
+      return false;
+    } else {
+      return startTime - this.startTime < interval && endTime - this.startTime 
< interval;
+    }
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof GroupByMonthFilter)) {
+      return false;
+    }
+    GroupByMonthFilter other = (GroupByMonthFilter) obj;
+    return this.interval == other.interval
+        && this.slidingStep == other.slidingStep
+        && this.startTime == other.startTime
+        && this.endTime == other.endTime
+        && this.isSlidingStepByMonth == other.isSlidingStepByMonth
+        && this.isIntervalByMonth == other.isIntervalByMonth;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        interval, slidingStep, startTime, endTime, isSlidingStepByMonth, 
isIntervalByMonth);
+  }
+
+  private void getNextIntervalAndSlidingStep() {
+    intervalCnt++;
+    if (isIntervalByMonth) {
+      calendar.setTimeInMillis(initialStartTime);
+      calendar.add(Calendar.MONTH, slidingStepsInMo * (intervalCnt - 1) + 
intervalInMo);
+      this.interval = calendar.getTimeInMillis() - startTime;
+    }
+    if (isSlidingStepByMonth) {
+      calendar.setTimeInMillis(initialStartTime);
+      calendar.add(Calendar.MONTH, slidingStepsInMo * intervalCnt);
+      this.slidingStep = calendar.getTimeInMillis() - startTime;
+    }
+  }
+}
diff --git 
a/tsfile/src/test/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilterTest.java
 
b/tsfile/src/test/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilterTest.java
new file mode 100644
index 0000000..7f11ebd
--- /dev/null
+++ 
b/tsfile/src/test/java/org/apache/iotdb/tsfile/read/filter/GroupByMonthFilterTest.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.tsfile.read.filter;
+
+import org.apache.iotdb.tsfile.file.metadata.statistics.LongStatistics;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class GroupByMonthFilterTest {
+
+  // The number of milliseconds in 30 days
+  private final long MS_TO_DAY = 86400_000L;
+  private final long MS_TO_MONTH = 30 * MS_TO_DAY;
+  // 1970-12-31 23:59:59
+  private final long END_TIME = 31507199000L;
+
+  /** Test filter with slidingStep = 2 month, and timeInterval = 1 month */
+  @Test
+  public void TestSatisfy1() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_MONTH, 2 * MS_TO_MONTH, 0, END_TIME, 
true, true);
+
+    // 1970-01-01 08:00:00, timezone = GMT+08:00
+    assertTrue(filter.satisfy(0, null));
+
+    // 1970-02-01 07:59:59
+    assertTrue(filter.satisfy(2678399000L, null));
+
+    // 1970-02-01 08:00:00
+    assertFalse(filter.satisfy(2678400000L, null));
+
+    // 1970-03-01 07:59:59
+    assertFalse(filter.satisfy(5097599000L, null));
+
+    // 1970-03-01 08:00:00
+    assertTrue(filter.satisfy(5097600000L, null));
+
+    // 1970-04-05 00:00:00
+    assertFalse(filter.satisfy(8092800000L, null));
+
+    // 1970-07-01 07:59:59
+    assertFalse(filter.satisfy(15638399000L, null));
+
+    // 1970-11-30 23:59:59
+    assertTrue(filter.satisfy(28828799000L, null));
+
+    // 1970-12-31 23:59:59
+    assertFalse(filter.satisfy(31507199000L, null));
+  }
+
+  /** Test filter with slidingStep = 1 month, and timeInterval = 1 month */
+  @Test
+  public void TestSatisfy2() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_MONTH, MS_TO_MONTH, 0, END_TIME, true, 
true);
+
+    // 1970-01-01 08:00:00, timezone = GMT+08:00
+    assertTrue(filter.satisfy(0, null));
+
+    // 1970-02-01 07:59:59
+    assertTrue(filter.satisfy(2678399000L, null));
+
+    // 1970-02-01 08:00:00
+    assertTrue(filter.satisfy(2678400000L, null));
+
+    // 1970-03-01 07:59:59
+    assertTrue(filter.satisfy(5097599000L, null));
+
+    // 1970-03-01 08:00:00
+    assertTrue(filter.satisfy(5097600000L, null));
+
+    // 1970-12-31 23:59:58
+    assertTrue(filter.satisfy(31507198000L, null));
+
+    // 1970-12-31 23:59:59
+    assertFalse(filter.satisfy(31507199000L, null));
+  }
+
+  /** Test filter with slidingStep = 1 month, and timeInterval = 1 day */
+  @Test
+  public void TestSatisfy3() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_DAY, MS_TO_MONTH, 0, END_TIME, true, 
false);
+
+    // 1970-01-01 08:00:00, timezone = GMT+08:00
+    assertTrue(filter.satisfy(0, null));
+
+    // 1970-01-02 07:59:59
+    assertTrue(filter.satisfy(86399000L, null));
+
+    // 1970-01-02 08:00:00
+    assertFalse(filter.satisfy(86400000L, null));
+
+    // 1970-02-01 07:59:59
+    assertFalse(filter.satisfy(2678399000L, null));
+
+    // 1970-02-01 08:00:00
+    assertTrue(filter.satisfy(2678400000L, null));
+
+    // 1970-03-01 08:00:00
+    assertTrue(filter.satisfy(5097600000L, null));
+
+    // 1970-12-01 08:00:00
+    assertTrue(filter.satisfy(28857600000L, null));
+
+    // 1970-12-31 23:59:59
+    assertFalse(filter.satisfy(31507199000L, null));
+  }
+
+  /** Test filter with slidingStep = 1 month, and timeInterval = 1 day */
+  @Test
+  public void TestSatisfyStartEndTime() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_DAY, MS_TO_MONTH, 0, END_TIME, true, 
false);
+
+    // 1970-01-01 08:00:00 - 1970-01-02 08:00:00, timezone = GMT+08:00
+    Statistics statistics = new LongStatistics();
+    statistics.setStartTime(0);
+    statistics.setEndTime(MS_TO_DAY);
+    assertTrue(filter.satisfy(statistics));
+
+    // 1970-01-01 20:00:00 - 1970-01-02 08:00:00
+    statistics.setStartTime(MS_TO_DAY / 2);
+    statistics.setEndTime(MS_TO_DAY);
+    assertTrue(filter.satisfy(statistics));
+
+    // 1970-01-01 20:00:00 - 1970-01-03 08:00:00
+    statistics.setStartTime(MS_TO_DAY / 2);
+    statistics.setEndTime(MS_TO_DAY * 2);
+    assertTrue(filter.satisfy(statistics));
+
+    // 1970-01-02 08:00:00 - 1970-01-03 08:00:00
+    statistics.setStartTime(MS_TO_DAY);
+    statistics.setEndTime(MS_TO_DAY * 2);
+    assertFalse(filter.satisfy(statistics));
+
+    // 1970-02-28 08:00:00 - 1970-03-01 07:59:59
+    statistics.setStartTime(5011200000L);
+    statistics.setEndTime(5097599000L);
+    assertFalse(filter.satisfy(statistics));
+
+    // 1970-03-01 09:00:00 - 1970-03-01 10:00:00
+    statistics.setStartTime(5101200000L);
+    statistics.setEndTime(5104800000L);
+    assertTrue(filter.satisfy(statistics));
+
+    // 1970-05-01 07:00:00 - 1970-05-01 08:00:00
+    statistics.setStartTime(10364400000L);
+    statistics.setEndTime(10368000000L);
+    assertTrue(filter.satisfy(statistics));
+
+    // 1970-01-02 07:59:59
+    assertTrue(filter.satisfy(86399000L, null));
+  }
+
+  /** Test filter with slidingStep = 1 month, and timeInterval = 1 day */
+  @Test
+  public void TestContainStartEndTime() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_DAY, MS_TO_MONTH, 0, END_TIME, true, 
false);
+
+    // 1970-01-01 08:00:00 - 1970-01-02 08:00:00, timezone = GMT+08:00
+    assertFalse(filter.containStartEndTime(0, MS_TO_DAY));
+
+    // 1970-01-01 08:00:00 - 1970-01-02 07:59:59
+    assertTrue(filter.containStartEndTime(0, MS_TO_DAY - 1000));
+
+    // 1970-02-01 07:59:59 - 1970-02-02 07:59:59
+    assertFalse(filter.containStartEndTime(2678399000L, 2764799000L));
+
+    // 1970-02-01 08:00:00 - 1970-02-02 07:59:59
+    assertTrue(filter.containStartEndTime(2678400000L, 2764799000L));
+
+    // 1970-02-01 08:00:00 - 1970-02-02 08:00:00
+    assertFalse(filter.containStartEndTime(2678400000L, 2764800000L));
+
+    // 1970-02-10 08:00:00 - 1970-02-11 08:00:00
+    assertFalse(filter.containStartEndTime(3456000000L, 3542400000L));
+
+    // 1970-10-01 10:00:00 - 1970-10-01 12:00:00
+    assertTrue(filter.containStartEndTime(23594400000L, 23601600000L));
+
+    // 1970-05-01 08:00:00 - 1970-05-01 10:00:00
+    assertTrue(filter.containStartEndTime(10368000000L, 10375200000L));
+
+    // 1970-03-01 08:00:00 - 1970-05-01 10:00:00
+    assertFalse(filter.containStartEndTime(5097600000L, 10375200000L));
+
+    // 1970-01-02 07:59:59
+    assertTrue(filter.satisfy(86399000L, null));
+  }
+
+  @Test
+  public void TestEquals() {
+    GroupByMonthFilter filter =
+        new GroupByMonthFilter(MS_TO_DAY, MS_TO_MONTH, 0, END_TIME, true, 
false);
+    Filter filter2 = filter.copy();
+    assertEquals(filter, filter2);
+    GroupByMonthFilter filter3 =
+        new GroupByMonthFilter(MS_TO_MONTH, MS_TO_MONTH, 0, END_TIME, true, 
true);
+    assertNotEquals(filter, filter3);
+  }
+}

Reply via email to