public static ResultColumn.ColumnType identifyOutputColumnType(
      Expression expression, boolean isRoot) {
    if (expression instanceof TernaryExpression) {
      ResultColumn.ColumnType firstType =
          identifyOutputColumnType(((TernaryExpression) expression).getFirstExpression(), false);
      ResultColumn.ColumnType secondType =
          identifyOutputColumnType(((TernaryExpression) expression).getSecondExpression(), false);
      ResultColumn.ColumnType thirdType =
          identifyOutputColumnType(((TernaryExpression) expression).getThirdExpression(), false);
      boolean rawFlag = false;
      boolean aggregationFlag = false;
      if (firstType == ResultColumn.ColumnType.RAW
          || secondType == ResultColumn.ColumnType.RAW
          || thirdType == ResultColumn.ColumnType.RAW) {
        rawFlag = true;
      }
      if (firstType == ResultColumn.ColumnType.AGGREGATION
          || secondType == ResultColumn.ColumnType.AGGREGATION
          || thirdType == ResultColumn.ColumnType.AGGREGATION) {
        aggregationFlag = true;
      }
      if (rawFlag && aggregationFlag) {
        throw new SemanticException(RAW_AGGREGATION_HYBRID_ERROR_MSG);
      }
      if (firstType == ResultColumn.ColumnType.CONSTANT
          && secondType == ResultColumn.ColumnType.CONSTANT
          && thirdType == ResultColumn.ColumnType.CONSTANT) {
        throw new SemanticException(CONSTANT_COLUMN_ERROR_MSG);
      }
      if (firstType != ResultColumn.ColumnType.CONSTANT) {
        return firstType;
      }
      if (secondType != ResultColumn.ColumnType.CONSTANT) {
        return secondType;
      }
      return thirdType;
    } else if (expression instanceof BinaryExpression) {
      ResultColumn.ColumnType leftType =
          identifyOutputColumnType(((BinaryExpression) expression).getLeftExpression(), false);
      ResultColumn.ColumnType rightType =
          identifyOutputColumnType(((BinaryExpression) expression).getRightExpression(), false);
      if ((leftType == ResultColumn.ColumnType.RAW
              && rightType == ResultColumn.ColumnType.AGGREGATION)
          || (leftType == ResultColumn.ColumnType.AGGREGATION
              && rightType == ResultColumn.ColumnType.RAW)) {
        throw new SemanticException(RAW_AGGREGATION_HYBRID_ERROR_MSG);
      }
      if (isRoot
          && leftType == ResultColumn.ColumnType.CONSTANT
          && rightType == ResultColumn.ColumnType.CONSTANT) {
        throw new SemanticException(CONSTANT_COLUMN_ERROR_MSG);
      }
      if (leftType != ResultColumn.ColumnType.CONSTANT) {
        return leftType;
      }
      return rightType;
    } else if (expression instanceof UnaryExpression) {
      return identifyOutputColumnType(((UnaryExpression) expression).getExpression(), false);
    } else if (expression instanceof FunctionExpression) {
      List<Expression> inputExpressions = expression.getExpressions();
      if (expression.isAggregationFunctionExpression()) {
        for (Expression inputExpression : inputExpressions) {
          if (identifyOutputColumnType(inputExpression, false)
              == ResultColumn.ColumnType.AGGREGATION) {
            throw new SemanticException(
                "Aggregation results cannot be as input of the aggregation function.");
          }
        }
        return ResultColumn.ColumnType.AGGREGATION;
      } else {
        ResultColumn.ColumnType checkedType = null;
        int lastCheckedIndex = 0;
        for (int i = 0; i < inputExpressions.size(); i++) {
          ResultColumn.ColumnType columnType =
              identifyOutputColumnType(inputExpressions.get(i), false);
          if (columnType != ResultColumn.ColumnType.CONSTANT) {
            checkedType = columnType;
            lastCheckedIndex = i;
            break;
          }
        }
        if (checkedType == null) {
          throw new SemanticException(
              String.format(
                  "Input of '%s' is illegal.",
                  ((FunctionExpression) expression).getFunctionName()));
        }
        for (int i = lastCheckedIndex; i < inputExpressions.size(); i++) {
          ResultColumn.ColumnType columnType =
              identifyOutputColumnType(inputExpressions.get(i), false);
          if (columnType != ResultColumn.ColumnType.CONSTANT && columnType != checkedType) {
            throw new SemanticException(
                String.format(
                    "Raw data and aggregation result hybrid input of '%s' is not supported.",
                    ((FunctionExpression) expression).getFunctionName()));
          }
        }
        return checkedType;
      }
    } else if (expression instanceof CaseWhenThenExpression) {
      // first, get all subexpression's type
      CaseWhenThenExpression caseExpression = (CaseWhenThenExpression) expression;
      List<ResultColumn.ColumnType> typeList =
          caseExpression.getExpressions().stream()
              .map(e -> identifyOutputColumnType(e, false))
              .collect(Collectors.toList());
      // if at least one subexpression is RAW, I'm RAW too
      boolean rawFlag =
          typeList.stream().anyMatch(columnType -> columnType == ResultColumn.ColumnType.RAW);
      // if at least one subexpression is AGGREGATION, I'm AGGREGATION too
      boolean aggregationFlag =
          typeList.stream()
              .anyMatch(columnType -> columnType == ResultColumn.ColumnType.AGGREGATION);
      // not allow RAW && AGGREGATION
      if (rawFlag && aggregationFlag) {
        throw new SemanticException(RAW_AGGREGATION_HYBRID_ERROR_MSG);
      }
      // not allow all const
      boolean allConst =
          typeList.stream().allMatch(columnType -> columnType == ResultColumn.ColumnType.CONSTANT);
      if (allConst) {
        throw new SemanticException(CONSTANT_COLUMN_ERROR_MSG);
      }
      for (ResultColumn.ColumnType type : typeList) {
        if (type != ResultColumn.ColumnType.CONSTANT) {
          return type;
        }
      }
      throw new IllegalArgumentException("shouldn't attach here");
    } else if (expression instanceof TimeSeriesOperand || expression instanceof TimestampOperand) {
      return ResultColumn.ColumnType.RAW;
    } else if (expression instanceof ConstantOperand || expression instanceof NullOperand) {
      return ResultColumn.ColumnType.CONSTANT;
    } else {
      throw new UnknownExpressionTypeException(expression.getExpressionType());
    }
  }
Check if all suffix paths in expression are measurements or one-level wildcards, used in ALIGN
BY DEVICE query or GroupByLevel query. If not, throw a {@link SemanticException}.

@param expression expression to be checked
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/ExpressionAnalyzer.java