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 e363388966a Add mode and variance aggregation support for table model
e363388966a is described below

commit e363388966ad47f0388ef768ab884dd70797600f
Author: Beyyes <[email protected]>
AuthorDate: Thu Oct 24 09:47:43 2024 +0800

    Add mode and variance aggregation support for table model
---
 .../db/it/IoTDBMultiIDsWithAttributesTableIT.java  |  36 ++
 .../relational/aggregation/AccumulatorFactory.java |  17 +
 .../aggregation/AggregationOperator.java           |   2 +-
 .../aggregation/TableModeAccumulator.java          | 480 +++++++++++++++++++++
 .../aggregation/TableVarianceAccumulator.java      | 231 ++++++++++
 .../source/relational/aggregation/Utils.java       |   9 +-
 .../plan/planner/TableOperatorGenerator.java       |  70 +--
 7 files changed, 813 insertions(+), 32 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiIDsWithAttributesTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiIDsWithAttributesTableIT.java
index 69f52276de6..ec659eddb42 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiIDsWithAttributesTableIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiIDsWithAttributesTableIT.java
@@ -1197,6 +1197,42 @@ public class IoTDBMultiIDsWithAttributesTableIT {
     tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
   }
 
+  @Test
+  public void modeTest() {
+    expectedHeader = buildHeaders(15);
+    sql =
+        "select 
mode(time),mode(device),mode(level),mode(attr1),mode(attr2),mode(num),mode(bignum),mode(floatnum),mode(date),mode(str),mode(bool),mode(date),mode(ts),mode(stringv),mode(doublenum)
 from table0 where device='d2' and level='l4' and time=80";
+    retArray =
+        new String[] {
+          
"1970-01-01T00:00:00.080Z,d2,l4,null,null,9,2147483646,43.12,null,apple,false,null,2024-09-20T06:15:35.000Z,test-string2,6666.7,",
+        };
+    tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
+
+    expectedHeader = buildHeaders(10);
+    sql =
+        "select 
mode(device),mode(level),mode(attr1),mode(attr2),mode(date),mode(bool),mode(date),mode(ts),mode(stringv),mode(doublenum)
 from table0 where device='d2' and level='l1'";
+    retArray =
+        new String[] {
+          
"d2,l1,d,c,null,false,null,2024-08-01T06:15:35.000Z,test-string3,null,",
+        };
+    tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
+  }
+
+  @Test
+  public void varianceTest() {
+    expectedHeader = buildHeaders(18);
+    sql =
+        "select \n"
+            + 
"round(variance(num),1),round(var_pop(num),1),round(var_samp(num),1),round(stddev(num),1),round(stddev_pop(num),1),round(stddev_samp(num),1),\n"
+            + 
"round(variance(floatnum),1),round(var_pop(floatnum),1),round(var_samp(floatnum),1),round(stddev(floatnum),1),round(stddev_pop(floatnum),1),round(stddev_samp(floatnum),1),\n"
+            + 
"round(variance(doublenum),1),round(var_pop(doublenum),1),round(var_samp(doublenum),1),round(stddev(doublenum),1),round(stddev_pop(doublenum),1),round(stddev_samp(doublenum),1)
 from table0 where device='d2' and level='l4'";
+    retArray =
+        new String[] {
+          
"16.0,10.7,16.0,4.0,3.3,4.0,50.0,33.3,50.0,7.1,5.8,7.1,null,0.0,null,null,0.0,null,",
+        };
+    tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
+  }
+
   // ==================================================================
   // ============================ Join Test ===========================
   // ==================================================================
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java
index 8875bcfacaa..5003883eb45 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AccumulatorFactory.java
@@ -20,6 +20,7 @@
 package 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation;
 
 import org.apache.iotdb.common.rpc.thrift.TAggregationType;
+import 
org.apache.iotdb.db.queryengine.execution.aggregation.VarianceAccumulator;
 import 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.grouped.GroupedAccumulator;
 import 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.grouped.GroupedAvgAccumulator;
 import 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.grouped.GroupedCountAccumulator;
@@ -179,6 +180,22 @@ public class AccumulatorFactory {
         return new TableMinByAccumulator(inputDataTypes.get(0), 
inputDataTypes.get(1));
       case EXTREME:
         return new ExtremeAccumulator(inputDataTypes.get(0));
+      case MODE:
+        return new TableModeAccumulator(inputDataTypes.get(0));
+      case STDDEV:
+      case STDDEV_SAMP:
+        return new TableVarianceAccumulator(
+            inputDataTypes.get(0), 
VarianceAccumulator.VarianceType.STDDEV_SAMP);
+      case STDDEV_POP:
+        return new TableVarianceAccumulator(
+            inputDataTypes.get(0), 
VarianceAccumulator.VarianceType.STDDEV_POP);
+      case VARIANCE:
+      case VAR_SAMP:
+        return new TableVarianceAccumulator(
+            inputDataTypes.get(0), VarianceAccumulator.VarianceType.VAR_SAMP);
+      case VAR_POP:
+        return new TableVarianceAccumulator(
+            inputDataTypes.get(0), VarianceAccumulator.VarianceType.VAR_POP);
       default:
         throw new IllegalArgumentException("Invalid Aggregation function: " + 
aggregationType);
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AggregationOperator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AggregationOperator.java
index 3da8ba5c84f..81abe6a62f1 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AggregationOperator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/AggregationOperator.java
@@ -151,7 +151,7 @@ public class AggregationOperator implements ProcessOperator 
{
   public long ramBytesUsed() {
     return INSTANCE_SIZE
         + MemoryEstimationHelper.getEstimatedSizeOfAccountableObject(child)
-        + 
aggregators.stream().mapToLong(TableAggregator::getEstimatedSize).count()
+        + 
aggregators.stream().mapToLong(TableAggregator::getEstimatedSize).sum()
         + 
MemoryEstimationHelper.getEstimatedSizeOfAccountableObject(operatorContext)
         + resultBuilder.getRetainedSizeInBytes();
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableModeAccumulator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableModeAccumulator.java
new file mode 100644
index 00000000000..3f90db0a1bd
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableModeAccumulator.java
@@ -0,0 +1,480 @@
+/*
+ * 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.queryengine.execution.operator.source.relational.aggregation;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+
+import org.apache.tsfile.block.column.Column;
+import org.apache.tsfile.block.column.ColumnBuilder;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.statistics.Statistics;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.utils.BytesUtils;
+import org.apache.tsfile.utils.RamUsageEstimator;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.Utils.UNSUPPORTED_TYPE_MESSAGE;
+import static 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation.Utils.serializeBinaryValue;
+
+public class TableModeAccumulator implements TableAccumulator {
+
+  private final int MAP_SIZE_THRESHOLD =
+      IoTDBDescriptor.getInstance().getConfig().getModeMapSizeThreshold();
+  private static final long INSTANCE_SIZE =
+      RamUsageEstimator.shallowSizeOfInstance(TableModeAccumulator.class);
+  private final TSDataType seriesDataType;
+
+  private Map<Boolean, Long> booleanCountMap;
+  private Map<Integer, Long> intCountMap;
+  private Map<Float, Long> floatCountMap;
+  private Map<Long, Long> longCountMap;
+  private Map<Double, Long> doubleCountMap;
+  private Map<Binary, Long> binaryCountMap;
+
+  public TableModeAccumulator(TSDataType seriesDataType) {
+    this.seriesDataType = seriesDataType;
+    switch (seriesDataType) {
+      case BOOLEAN:
+        booleanCountMap = new HashMap<>();
+        break;
+      case INT32:
+      case DATE:
+        intCountMap = new HashMap<>();
+        break;
+      case FLOAT:
+        floatCountMap = new HashMap<>();
+        break;
+      case INT64:
+      case TIMESTAMP:
+        longCountMap = new HashMap<>();
+        break;
+      case DOUBLE:
+        doubleCountMap = new HashMap<>();
+        break;
+      case TEXT:
+      case STRING:
+      case BLOB:
+        binaryCountMap = new HashMap<>();
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            String.format(UNSUPPORTED_TYPE_MESSAGE, seriesDataType));
+    }
+  }
+
+  @Override
+  public long getEstimatedSize() {
+    return INSTANCE_SIZE;
+  }
+
+  @Override
+  public TableAccumulator copy() {
+    return new TableModeAccumulator(seriesDataType);
+  }
+
+  @Override
+  public void addInput(Column[] arguments) {
+    switch (seriesDataType) {
+      case BOOLEAN:
+        addBooleanInput(arguments[0]);
+        break;
+      case INT32:
+      case DATE:
+        addIntInput(arguments[0]);
+        break;
+      case FLOAT:
+        addFloatInput(arguments[0]);
+        break;
+      case INT64:
+      case TIMESTAMP:
+        addLongInput(arguments[0]);
+        break;
+      case DOUBLE:
+        addDoubleInput(arguments[0]);
+        break;
+      case TEXT:
+      case STRING:
+      case BLOB:
+        addBinaryInput(arguments[0]);
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            String.format(UNSUPPORTED_TYPE_MESSAGE, seriesDataType));
+    }
+  }
+
+  @Override
+  public void addIntermediate(Column argument) {
+    for (int i = 0; i < argument.getPositionCount(); i++) {
+      if (argument.isNull(i)) {
+        continue;
+      }
+
+      byte[] bytes = argument.getBinary(i).getValues();
+      deserializeAndMergeCountMap(bytes);
+    }
+  }
+
+  @Override
+  public void evaluateIntermediate(ColumnBuilder columnBuilder) {
+    columnBuilder.writeBinary(new Binary(serializeCountMap()));
+  }
+
+  @Override
+  public void evaluateFinal(ColumnBuilder columnBuilder) {
+    switch (seriesDataType) {
+      case BOOLEAN:
+        if (booleanCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Boolean> maxKey =
+              booleanCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeBoolean);
+        }
+        break;
+      case INT32:
+      case DATE:
+        if (intCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Integer> maxKey =
+              intCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeInt);
+        }
+        break;
+      case FLOAT:
+        if (floatCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Float> maxKey =
+              floatCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeFloat);
+        }
+        break;
+      case INT64:
+      case TIMESTAMP:
+        if (longCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Long> maxKey =
+              longCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeLong);
+        }
+        break;
+      case DOUBLE:
+        if (doubleCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Double> maxKey =
+              doubleCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeDouble);
+        }
+        break;
+      case TEXT:
+      case STRING:
+      case BLOB:
+        if (binaryCountMap.isEmpty()) {
+          columnBuilder.appendNull();
+        } else {
+          Optional<Binary> maxKey =
+              binaryCountMap.entrySet().stream()
+                  .max(Map.Entry.comparingByValue())
+                  .map(Map.Entry::getKey);
+          maxKey.ifPresent(columnBuilder::writeBinary);
+        }
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            String.format(UNSUPPORTED_TYPE_MESSAGE, seriesDataType));
+    }
+  }
+
+  @Override
+  public boolean hasFinalResult() {
+    return false;
+  }
+
+  @Override
+  public void addStatistics(Statistics[] statistics) {
+    throw new UnsupportedOperationException(getClass().getName());
+  }
+
+  @Override
+  public void reset() {
+    if (booleanCountMap != null) {
+      booleanCountMap.clear();
+    }
+    if (intCountMap != null) {
+      intCountMap.clear();
+    }
+    if (floatCountMap != null) {
+      floatCountMap.clear();
+    }
+    if (longCountMap != null) {
+      longCountMap.clear();
+    }
+    if (doubleCountMap != null) {
+      doubleCountMap.clear();
+    }
+    if (binaryCountMap != null) {
+      binaryCountMap.clear();
+    }
+  }
+
+  private byte[] serializeCountMap() {
+    byte[] bytes;
+    int offset = 0;
+
+    switch (seriesDataType) {
+      case BOOLEAN:
+        bytes = new byte[4 + (1 + 8) * booleanCountMap.size()];
+        BytesUtils.intToBytes(booleanCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Boolean, Long> entry : booleanCountMap.entrySet()) {
+          BytesUtils.boolToBytes(entry.getKey(), bytes, offset);
+          offset += 1;
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      case INT32:
+      case DATE:
+        bytes = new byte[4 + (4 + 8) * intCountMap.size()];
+        BytesUtils.intToBytes(intCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Integer, Long> entry : intCountMap.entrySet()) {
+          BytesUtils.intToBytes(entry.getKey(), bytes, offset);
+          offset += 4;
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      case FLOAT:
+        bytes = new byte[4 + (4 + 8) * floatCountMap.size()];
+        BytesUtils.intToBytes(floatCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Float, Long> entry : floatCountMap.entrySet()) {
+          BytesUtils.floatToBytes(entry.getKey(), bytes, offset);
+          offset += 4;
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      case INT64:
+      case TIMESTAMP:
+        bytes = new byte[4 + (8 + 8) * longCountMap.size()];
+        BytesUtils.intToBytes(longCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Long, Long> entry : longCountMap.entrySet()) {
+          BytesUtils.longToBytes(entry.getKey(), bytes, offset);
+          offset += 8;
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      case DOUBLE:
+        bytes = new byte[4 + (8 + 8) * doubleCountMap.size()];
+        BytesUtils.intToBytes(doubleCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Double, Long> entry : doubleCountMap.entrySet()) {
+          BytesUtils.doubleToBytes(entry.getKey(), bytes, offset);
+          offset += 8;
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      case TEXT:
+      case STRING:
+      case BLOB:
+        bytes =
+            new byte
+                [4
+                    + (8 + 4) * binaryCountMap.size()
+                    + binaryCountMap.keySet().stream()
+                        .mapToInt(key -> key.getValues().length)
+                        .sum()];
+        BytesUtils.intToBytes(binaryCountMap.size(), bytes, offset);
+        offset += 4;
+        for (Map.Entry<Binary, Long> entry : binaryCountMap.entrySet()) {
+          Binary binary = entry.getKey();
+          serializeBinaryValue(binary, bytes, offset);
+          offset += (4 + binary.getLength());
+          BytesUtils.longToBytes(entry.getValue(), bytes, offset);
+          offset += 8;
+        }
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            String.format(UNSUPPORTED_TYPE_MESSAGE, seriesDataType));
+    }
+
+    return bytes;
+  }
+
+  private void deserializeAndMergeCountMap(byte[] bytes) {
+    int offset = 0;
+    int size = BytesUtils.bytesToInt(bytes, offset);
+    offset += 4;
+
+    switch (seriesDataType) {
+      case BOOLEAN:
+        for (int i = 0; i < size; i++) {
+          boolean key = BytesUtils.bytesToBool(bytes, offset);
+          offset += 1;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          booleanCountMap.compute(key, (k, v) -> v == null ? count : v + 
count);
+        }
+        break;
+      case INT32:
+      case DATE:
+        for (int i = 0; i < size; i++) {
+          int key = BytesUtils.bytesToInt(bytes, offset);
+          offset += 4;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          intCountMap.compute(key, (k, v) -> v == null ? count : v + count);
+        }
+        break;
+      case FLOAT:
+        for (int i = 0; i < size; i++) {
+          float key = BytesUtils.bytesToFloat(bytes, offset);
+          offset += 4;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          floatCountMap.compute(key, (k, v) -> v == null ? count : v + count);
+        }
+        break;
+      case INT64:
+      case TIMESTAMP:
+        for (int i = 0; i < size; i++) {
+          long key = BytesUtils.bytesToLong(bytes, offset);
+          offset += 8;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          longCountMap.compute(key, (k, v) -> v == null ? count : v + count);
+        }
+        break;
+      case DOUBLE:
+        for (int i = 0; i < size; i++) {
+          double key = BytesUtils.bytesToDouble(bytes, offset);
+          offset += 8;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          doubleCountMap.compute(key, (k, v) -> v == null ? count : v + count);
+        }
+        break;
+      case TEXT:
+      case STRING:
+      case BLOB:
+        for (int i = 0; i < size; i++) {
+          int length = BytesUtils.bytesToInt(bytes, offset);
+          offset += 4;
+          Binary binaryVal = new Binary(BytesUtils.subBytes(bytes, offset, 
length));
+          offset += length;
+          long count = BytesUtils.bytesToLongFromOffset(bytes, 8, offset);
+          offset += 8;
+          binaryCountMap.compute(binaryVal, (k, v) -> v == null ? count : v + 
count);
+        }
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            String.format(UNSUPPORTED_TYPE_MESSAGE, seriesDataType));
+    }
+  }
+
+  private void addBooleanInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        booleanCountMap.compute(column.getBoolean(i), (k, v) -> v == null ? 1 
: v + 1);
+        if (booleanCountMap.size() > MAP_SIZE_THRESHOLD) {
+          checkMapSize(booleanCountMap.size());
+        }
+      }
+    }
+  }
+
+  private void addIntInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        intCountMap.compute(column.getInt(i), (k, v) -> v == null ? 1 : v + 1);
+        checkMapSize(intCountMap.size());
+      }
+    }
+  }
+
+  private void addFloatInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        floatCountMap.compute(column.getFloat(i), (k, v) -> v == null ? 1 : v 
+ 1);
+        checkMapSize(floatCountMap.size());
+      }
+    }
+  }
+
+  private void addLongInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        longCountMap.compute(column.getLong(i), (k, v) -> v == null ? 1 : v + 
1);
+        checkMapSize(longCountMap.size());
+      }
+    }
+  }
+
+  private void addDoubleInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        doubleCountMap.compute(column.getDouble(i), (k, v) -> v == null ? 1 : 
v + 1);
+        checkMapSize(doubleCountMap.size());
+      }
+    }
+  }
+
+  private void addBinaryInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (!column.isNull(i)) {
+        binaryCountMap.compute(column.getBinary(i), (k, v) -> v == null ? 1 : 
v + 1);
+        checkMapSize(binaryCountMap.size());
+      }
+    }
+  }
+
+  private void checkMapSize(int size) {
+    if (size > MAP_SIZE_THRESHOLD) {
+      throw new RuntimeException(
+          String.format(
+              "distinct values has exceeded the threshold %s when calculate 
Mode",
+              MAP_SIZE_THRESHOLD));
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableVarianceAccumulator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableVarianceAccumulator.java
new file mode 100644
index 00000000000..0eb60b15991
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/TableVarianceAccumulator.java
@@ -0,0 +1,231 @@
+/*
+ * 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.queryengine.execution.operator.source.relational.aggregation;
+
+import 
org.apache.iotdb.db.queryengine.execution.aggregation.VarianceAccumulator;
+
+import org.apache.tsfile.block.column.Column;
+import org.apache.tsfile.block.column.ColumnBuilder;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.statistics.Statistics;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.utils.BytesUtils;
+import org.apache.tsfile.utils.RamUsageEstimator;
+import org.apache.tsfile.write.UnSupportedDataTypeException;
+
+public class TableVarianceAccumulator implements TableAccumulator {
+
+  private static final long INSTANCE_SIZE =
+      RamUsageEstimator.shallowSizeOfInstance(TableVarianceAccumulator.class);
+  private final TSDataType seriesDataType;
+  private final VarianceAccumulator.VarianceType varianceType;
+
+  private long count;
+  private double mean;
+  private double m2;
+
+  public TableVarianceAccumulator(
+      TSDataType seriesDataType, VarianceAccumulator.VarianceType 
varianceType) {
+    this.seriesDataType = seriesDataType;
+    this.varianceType = varianceType;
+  }
+
+  @Override
+  public long getEstimatedSize() {
+    return INSTANCE_SIZE;
+  }
+
+  @Override
+  public TableAccumulator copy() {
+    return new TableVarianceAccumulator(seriesDataType, varianceType);
+  }
+
+  @Override
+  public void addInput(Column[] arguments) {
+    switch (seriesDataType) {
+      case INT32:
+        addIntInput(arguments[0]);
+        return;
+      case INT64:
+        addLongInput(arguments[0]);
+        return;
+      case FLOAT:
+        addFloatInput(arguments[0]);
+        return;
+      case DOUBLE:
+        addDoubleInput(arguments[0]);
+        return;
+      case TEXT:
+      case BLOB:
+      case BOOLEAN:
+      case DATE:
+      case STRING:
+      case TIMESTAMP:
+      default:
+        throw new UnSupportedDataTypeException(
+            String.format("Unsupported data type in aggregation variance : 
%s", seriesDataType));
+    }
+  }
+
+  @Override
+  public void addIntermediate(Column argument) {
+    for (int i = 0; i < argument.getPositionCount(); i++) {
+      if (argument.isNull(i)) {
+        continue;
+      }
+
+      byte[] bytes = argument.getBinary(i).getValues();
+      long intermediateCount = BytesUtils.bytesToLong(bytes, Long.BYTES);
+      double intermediateMean = BytesUtils.bytesToDouble(bytes, Long.BYTES);
+      double intermediateM2 = BytesUtils.bytesToDouble(bytes, (Long.BYTES + 
Double.BYTES));
+
+      long newCount = count + intermediateCount;
+      double newMean = ((intermediateCount * intermediateMean) + (count * 
mean)) / newCount;
+      double delta = intermediateMean - mean;
+
+      m2 = m2 + intermediateM2 + delta * delta * intermediateCount * count / 
newCount;
+      count = newCount;
+      mean = newMean;
+    }
+  }
+
+  @Override
+  public void evaluateIntermediate(ColumnBuilder columnBuilder) {
+    if (count == 0) {
+      columnBuilder.appendNull();
+    } else {
+      byte[] bytes = new byte[24];
+      BytesUtils.longToBytes(count, bytes, 0);
+      BytesUtils.doubleToBytes(mean, bytes, Long.BYTES);
+      BytesUtils.doubleToBytes(m2, bytes, Long.BYTES + Double.BYTES);
+      columnBuilder.writeBinary(new Binary(bytes));
+    }
+  }
+
+  @Override
+  public void evaluateFinal(ColumnBuilder columnBuilder) {
+    switch (varianceType) {
+      case STDDEV_POP:
+        if (count == 0) {
+          columnBuilder.appendNull();
+        } else {
+          columnBuilder.writeDouble(Math.sqrt(m2 / count));
+        }
+        break;
+      case STDDEV_SAMP:
+        if (count < 2) {
+          columnBuilder.appendNull();
+        } else {
+          columnBuilder.writeDouble(Math.sqrt(m2 / (count - 1)));
+        }
+        break;
+      case VAR_POP:
+        if (count == 0) {
+          columnBuilder.appendNull();
+        } else {
+          columnBuilder.writeDouble(m2 / count);
+        }
+        break;
+      case VAR_SAMP:
+        if (count < 2) {
+          columnBuilder.appendNull();
+        } else {
+          columnBuilder.writeDouble(m2 / (count - 1));
+        }
+        break;
+      default:
+        throw new EnumConstantNotPresentException(
+            VarianceAccumulator.VarianceType.class, varianceType.name());
+    }
+  }
+
+  @Override
+  public boolean hasFinalResult() {
+    return false;
+  }
+
+  @Override
+  public void addStatistics(Statistics[] statistics) {
+    throw new UnsupportedOperationException(getClass().getName());
+  }
+
+  @Override
+  public void reset() {
+    count = 0;
+    mean = 0.0;
+    m2 = 0.0;
+  }
+
+  private void addIntInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (column.isNull(i)) {
+        continue;
+      }
+
+      int value = column.getInt(i);
+      count++;
+      double delta = value - mean;
+      mean += delta / count;
+      m2 += delta * (value - mean);
+    }
+  }
+
+  private void addLongInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (column.isNull(i)) {
+        continue;
+      }
+
+      long value = column.getLong(i);
+      count++;
+      double delta = value - mean;
+      mean += delta / count;
+      m2 += delta * (value - mean);
+    }
+  }
+
+  private void addFloatInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (column.isNull(i)) {
+        continue;
+      }
+
+      float value = column.getFloat(i);
+      count++;
+      double delta = value - mean;
+      mean += delta / count;
+      m2 += delta * (value - mean);
+    }
+  }
+
+  private void addDoubleInput(Column column) {
+    for (int i = 0; i < column.getPositionCount(); i++) {
+      if (column.isNull(i)) {
+        continue;
+      }
+
+      double value = column.getDouble(i);
+      count++;
+      double delta = value - mean;
+      mean += delta / count;
+      m2 += delta * (value - mean);
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/Utils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/Utils.java
index 85c79fd8ea0..99252b9606d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/Utils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/aggregation/Utils.java
@@ -20,6 +20,7 @@
 package 
org.apache.iotdb.db.queryengine.execution.operator.source.relational.aggregation;
 
 import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.Binary;
 import org.apache.tsfile.utils.BytesUtils;
 import org.apache.tsfile.utils.TsPrimitiveType;
 import org.apache.tsfile.write.UnSupportedDataTypeException;
@@ -29,7 +30,7 @@ import static org.apache.tsfile.enums.TSDataType.STRING;
 import static org.apache.tsfile.enums.TSDataType.TEXT;
 
 public class Utils {
-  public static final String UNSUPPORTED_TYPE_MESSAGE = "Unsupported data type 
in serialize : %s";
+  public static final String UNSUPPORTED_TYPE_MESSAGE = "Unsupported data type 
: %s";
 
   private Utils() {}
 
@@ -70,6 +71,12 @@ public class Utils {
     }
   }
 
+  public static void serializeBinaryValue(Binary binary, byte[] valueBytes, 
int offset) {
+    BytesUtils.intToBytes(binary.getValues().length, valueBytes, offset);
+    offset += 4;
+    System.arraycopy(binary.getValues(), 0, valueBytes, offset, 
binary.getValues().length);
+  }
+
   public static byte[] serializeTimeValue(
       TSDataType seriesDataType, long time, TsPrimitiveType value) {
     byte[] valueBytes = new byte[8 + calcTypeSize(seriesDataType, value)];
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
index fbd146ab82d..1d02608b317 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
@@ -188,14 +188,16 @@ import static 
org.apache.iotdb.db.queryengine.plan.planner.OperatorTreeGenerator
 import static 
org.apache.iotdb.db.queryengine.plan.relational.metadata.TableBuiltinAggregationFunction.getAggregationTypeByFuncName;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.SortOrder.ASC_NULLS_LAST;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.type.InternalTypeManager.getTSDataType;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.AVG;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.COUNT;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.EXTREME;
 import static org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_AGGREGATION;
 import static 
org.apache.iotdb.db.utils.constant.SqlConstant.FIRST_BY_AGGREGATION;
 import static org.apache.iotdb.db.utils.constant.SqlConstant.LAST_AGGREGATION;
 import static 
org.apache.iotdb.db.utils.constant.SqlConstant.LAST_BY_AGGREGATION;
 import static org.apache.iotdb.db.utils.constant.SqlConstant.MAX;
-import static org.apache.iotdb.db.utils.constant.SqlConstant.MAX_BY;
 import static org.apache.iotdb.db.utils.constant.SqlConstant.MIN;
-import static org.apache.iotdb.db.utils.constant.SqlConstant.MIN_BY;
+import static org.apache.iotdb.db.utils.constant.SqlConstant.SUM;
 import static org.apache.tsfile.read.common.type.TimestampType.TIMESTAMP;
 
 /** This Visitor is responsible for transferring Table PlanNode Tree to Table 
Operator Tree. */
@@ -1686,37 +1688,45 @@ public class TableOperatorGenerator extends 
PlanVisitor<Operator, LocalExecution
       Symbol argument = Symbol.from(aggregation.getArguments().get(0));
       Type argumentType = node.getAssignments().get(argument).getType();
 
-      if (MAX_BY.equals(funcName) || MIN_BY.equals(funcName)) {
-        canUseStatistic = false;
-      } else if (MAX.equals(funcName) || MIN.equals(funcName)) {
-        if (BlobType.BLOB.equals(argumentType)
-            || BinaryType.TEXT.equals(argumentType)
-            || BooleanType.BOOLEAN.equals(argumentType)) {
-          canUseStatistic = false;
-        }
-      } else if (FIRST_AGGREGATION.equals(funcName)
-          || LAST_AGGREGATION.equals(funcName)
-          || LAST_BY_AGGREGATION.equals(funcName)
-          || FIRST_BY_AGGREGATION.equals(funcName)) {
-        if (FIRST_AGGREGATION.equals(funcName) || 
FIRST_BY_AGGREGATION.equals(funcName)) {
-          ascendingCount++;
-        } else {
-          descendingCount++;
-        }
-
-        // first/last/first_by/last_by aggregation with BLOB type can not use 
statistics
+      switch (funcName) {
+        case COUNT:
+        case AVG:
+        case SUM:
+        case EXTREME:
+          break;
+        case MAX:
+        case MIN:
+          if (BlobType.BLOB.equals(argumentType)
+              || BinaryType.TEXT.equals(argumentType)
+              || BooleanType.BOOLEAN.equals(argumentType)) {
+            canUseStatistic = false;
+          }
+          break;
+        case FIRST_AGGREGATION:
+        case LAST_AGGREGATION:
+        case LAST_BY_AGGREGATION:
+        case FIRST_BY_AGGREGATION:
+          if (FIRST_AGGREGATION.equals(funcName) || 
FIRST_BY_AGGREGATION.equals(funcName)) {
+            ascendingCount++;
+          } else {
+            descendingCount++;
+          }
 
-        if (BlobType.BLOB.equals(argumentType)) {
-          canUseStatistic = false;
-          continue;
-        }
+          // first/last/first_by/last_by aggregation with BLOB type can not 
use statistics
+          if (BlobType.BLOB.equals(argumentType)) {
+            canUseStatistic = false;
+            break;
+          }
 
-        // only last_by(time, x) or last_by(x,time) can use statistic
-        if ((LAST_BY_AGGREGATION.equals(funcName) || 
FIRST_BY_AGGREGATION.equals(funcName))
-            && !isTimeColumn(aggregation.getArguments().get(0))
-            && !isTimeColumn(aggregation.getArguments().get(1))) {
+          // only last_by(time, x) or last_by(x,time) can use statistic
+          if ((LAST_BY_AGGREGATION.equals(funcName) || 
FIRST_BY_AGGREGATION.equals(funcName))
+              && !isTimeColumn(aggregation.getArguments().get(0))
+              && !isTimeColumn(aggregation.getArguments().get(1))) {
+            canUseStatistic = false;
+          }
+          break;
+        default:
           canUseStatistic = false;
-        }
       }
     }
 


Reply via email to