/*
 * 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.plan.parser;

import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
import org.apache.iotdb.common.rpc.thrift.TTimedQuota;
import org.apache.iotdb.common.rpc.thrift.ThrottleType;
import org.apache.iotdb.commons.auth.entity.PrivilegeType;
import org.apache.iotdb.commons.cluster.NodeStatus;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.cq.TimeoutPolicy;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.schema.filter.SchemaFilter;
import org.apache.iotdb.commons.schema.filter.SchemaFilterFactory;
import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
import org.apache.iotdb.commons.utils.PathUtils;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.exception.sql.SemanticException;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.ConstantContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.CountDatabasesContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.CountDevicesContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.CountNodesContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.CountTimeseriesContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.CreateFunctionContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.DropFunctionContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.ExpressionContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.GroupByAttributeClauseContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.IdentifierContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParser.ShowFunctionsContext;
import org.apache.iotdb.db.qp.sql.IoTDBSqlParserBaseVisitor;
import org.apache.iotdb.db.queryengine.common.header.ColumnHeaderConstant;
import org.apache.iotdb.db.queryengine.execution.operator.window.WindowType;
import org.apache.iotdb.db.queryengine.plan.analyze.ExpressionAnalyzer;
import org.apache.iotdb.db.queryengine.plan.expression.Expression;
import org.apache.iotdb.db.queryengine.plan.expression.ExpressionType;
import org.apache.iotdb.db.queryengine.plan.expression.binary.AdditionExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.CompareBinaryExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.DivisionExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.EqualToExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.GreaterEqualExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.GreaterThanExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.LessEqualExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.LessThanExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.LogicAndExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.LogicOrExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.ModuloExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.MultiplicationExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.NonEqualExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.SubtractionExpression;
import org.apache.iotdb.db.queryengine.plan.expression.binary.WhenThenExpression;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.ConstantOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.NullOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimeSeriesOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimestampOperand;
import org.apache.iotdb.db.queryengine.plan.expression.multi.FunctionExpression;
import org.apache.iotdb.db.queryengine.plan.expression.multi.builtin.BuiltInScalarFunctionHelperFactory;
import org.apache.iotdb.db.queryengine.plan.expression.other.CaseWhenThenExpression;
import org.apache.iotdb.db.queryengine.plan.expression.ternary.BetweenExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.InExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.IsNullExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.LikeExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.LogicNotExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.NegationExpression;
import org.apache.iotdb.db.queryengine.plan.expression.unary.RegularExpression;
import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
import org.apache.iotdb.db.queryengine.plan.statement.Statement;
import org.apache.iotdb.db.queryengine.plan.statement.StatementType;
import org.apache.iotdb.db.queryengine.plan.statement.component.FillComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.FillPolicy;
import org.apache.iotdb.db.queryengine.plan.statement.component.FromComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByConditionComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByCountComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByLevelComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupBySessionComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByTagComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByTimeComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByVariationComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.HavingCondition;
import org.apache.iotdb.db.queryengine.plan.statement.component.IntoComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.IntoItem;
import org.apache.iotdb.db.queryengine.plan.statement.component.NullOrdering;
import org.apache.iotdb.db.queryengine.plan.statement.component.OrderByComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.OrderByKey;
import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
import org.apache.iotdb.db.queryengine.plan.statement.component.ResultColumn;
import org.apache.iotdb.db.queryengine.plan.statement.component.ResultSetFormat;
import org.apache.iotdb.db.queryengine.plan.statement.component.SelectComponent;
import org.apache.iotdb.db.queryengine.plan.statement.component.SortItem;
import org.apache.iotdb.db.queryengine.plan.statement.component.WhereCondition;
import org.apache.iotdb.db.queryengine.plan.statement.crud.DeleteDataStatement;
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertStatement;
import org.apache.iotdb.db.queryengine.plan.statement.crud.LoadTsFileStatement;
import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement;
import org.apache.iotdb.db.queryengine.plan.statement.literal.BooleanLiteral;
import org.apache.iotdb.db.queryengine.plan.statement.literal.DoubleLiteral;
import org.apache.iotdb.db.queryengine.plan.statement.literal.Literal;
import org.apache.iotdb.db.queryengine.plan.statement.literal.LongLiteral;
import org.apache.iotdb.db.queryengine.plan.statement.literal.StringLiteral;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDevicesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountLevelTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountNodesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSlotListStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateAlignedTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateContinuousQueryStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateFunctionStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateTriggerStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DatabaseSchemaStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DeleteDatabaseStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DeleteTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DropContinuousQueryStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DropFunctionStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.DropTriggerStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.GetRegionIdStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.GetSeriesSlotListStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.GetTimeSlotListStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.MigrateRegionStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.SetTTLStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowChildNodesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowChildPathsStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowClusterIdStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowClusterStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowConfigNodesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowContinuousQueriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowCurrentTimestampStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowDataNodesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowDatabaseStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowDevicesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowFunctionsStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowRegionStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowTTLStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowTimeSeriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowTriggersStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.ShowVariablesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.UnSetTTLStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.CreatePipePluginStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.CreatePipeStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.DropPipePluginStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.DropPipeStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.ShowPipePluginsStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.ShowPipesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.StartPipeStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.pipe.StopPipeStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ActivateTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.AlterSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.CreateSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.DeactivateTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.DropSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.SetSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ShowNodesInSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ShowPathSetTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ShowPathsUsingTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ShowSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.template.UnsetSchemaTemplateStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.AlterLogicalViewStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.CreateLogicalViewStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.DeleteLogicalViewStatement;
import org.apache.iotdb.db.queryengine.plan.statement.metadata.view.ShowLogicalViewStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.ClearCacheStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.ExplainStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.FlushStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.KillQueryStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.LoadConfigurationStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.MergeStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.SetSystemStatusStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.ShowQueriesStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.ShowVersionStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.StartRepairDataStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.StopRepairDataStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetSpaceQuotaStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetThrottleQuotaStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowSpaceQuotaStatement;
import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowThrottleQuotaStatement;
import org.apache.iotdb.db.schemaengine.template.TemplateAlterOperationType;
import org.apache.iotdb.db.utils.DateTimeUtils;
import org.apache.iotdb.db.utils.TimestampPrecisionUtils;
import org.apache.iotdb.db.utils.constant.SqlConstant;
import org.apache.iotdb.trigger.api.enums.TriggerEvent;
import org.apache.iotdb.trigger.api.enums.TriggerType;
import org.apache.iotdb.tsfile.common.conf.TSFileDescriptor;
import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
import org.apache.iotdb.tsfile.read.common.TimeRange;
import org.apache.iotdb.tsfile.utils.TimeDuration;

import com.google.common.collect.ImmutableSet;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.apache.iotdb.commons.schema.SchemaConstant.ALL_RESULT_NODES;
import static org.apache.iotdb.db.queryengine.plan.optimization.LimitOffsetPushDown.canPushDownLimitOffsetToGroupByTime;
import static org.apache.iotdb.db.queryengine.plan.optimization.LimitOffsetPushDown.pushDownLimitOffsetToTimeParameter;
import static org.apache.iotdb.db.utils.TimestampPrecisionUtils.currPrecision;
import static org.apache.iotdb.db.utils.constant.SqlConstant.CAST_FUNCTION;
import static org.apache.iotdb.db.utils.constant.SqlConstant.CAST_TYPE;
import static org.apache.iotdb.db.utils.constant.SqlConstant.REPLACE_FROM;
import static org.apache.iotdb.db.utils.constant.SqlConstant.REPLACE_FUNCTION;
import static org.apache.iotdb.db.utils.constant.SqlConstant.REPLACE_TO;
import static org.apache.iotdb.db.utils.constant.SqlConstant.ROUND_FUNCTION;
import static org.apache.iotdb.db.utils.constant.SqlConstant.ROUND_PLACES;
import static org.apache.iotdb.db.utils.constant.SqlConstant.SUBSTRING_FUNCTION;
import static org.apache.iotdb.db.utils.constant.SqlConstant.SUBSTRING_IS_STANDARD;
import static org.apache.iotdb.db.utils.constant.SqlConstant.SUBSTRING_LENGTH;
import static org.apache.iotdb.db.utils.constant.SqlConstant.SUBSTRING_START;

/** Parse AST to Statement. */
public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {

  private static final IoTDBConfig CONFIG = IoTDBDescriptor.getInstance().getConfig();

  private static final String DELETE_RANGE_ERROR_MSG =
      "For delete statement, where clause can only contain atomic expressions like : "
          + "time > XXX, time <= XXX, or two atomic expressions connected by 'AND'";
  private static final String DELETE_ONLY_SUPPORT_TIME_EXP_ERROR_MSG =
      "For delete statement, where clause can only contain time expressions, "
          + "value filter is not currently supported.";

  private static final String GROUP_BY_COMMON_ONLY_ONE_MSG =
      "Only one of group by time or group by variation/series/session can be supported at a time";

  private static final String LIMIT_CONFIGURATION_ENABLED_ERROR_MSG =
      "Limit configuration is not enabled, please enable it first.";

  private static final String NODE_NAME_IN_INTO_PATH_MATCHER = "([a-zA-Z0-9_${}\\u2E80-\\u9FFF]+)";
  private static final Pattern NODE_NAME_IN_INTO_PATH_PATTERN =
      Pattern.compile(NODE_NAME_IN_INTO_PATH_MATCHER);

  private static final String IGNORENULL = "IgnoreNull";
  private ZoneId zoneId;

  private boolean useWildcard = false;

  private boolean lastLevelUseWildcard = false;

  public void setZoneId(ZoneId zoneId) {
    this.zoneId = zoneId;
  }

  /** Top Level Description. */
  @Override
  public Statement visitSingleStatement(IoTDBSqlParser.SingleStatementContext ctx) {
    Statement statement = visit(ctx.statement());
    if (ctx.DEBUG() != null) {
      statement.setDebug(true);
    }
    return statement;
  }

  /** Data Definition Language (DDL). */

  // Create Timeseries ========================================================================
  @Override
  public Statement visitCreateNonAlignedTimeseries(
      IoTDBSqlParser.CreateNonAlignedTimeseriesContext ctx) {
    CreateTimeSeriesStatement createTimeSeriesStatement = new CreateTimeSeriesStatement();
    createTimeSeriesStatement.setPath(parseFullPath(ctx.fullPath()));
    if (ctx.attributeClauses() != null) {
      parseAttributeClausesForCreateNonAlignedTimeSeries(
          ctx.attributeClauses(), createTimeSeriesStatement);
    }
    return createTimeSeriesStatement;
  }

  @Override
  public Statement visitCreateAlignedTimeseries(IoTDBSqlParser.CreateAlignedTimeseriesContext ctx) {
    CreateAlignedTimeSeriesStatement createAlignedTimeSeriesStatement =
        new CreateAlignedTimeSeriesStatement();
    createAlignedTimeSeriesStatement.setDevicePath(parseFullPath(ctx.fullPath()));
    parseAlignedMeasurements(ctx.alignedMeasurements(), createAlignedTimeSeriesStatement);
    return createAlignedTimeSeriesStatement;
  }

  public void parseAlignedMeasurements(
      IoTDBSqlParser.AlignedMeasurementsContext ctx,
      CreateAlignedTimeSeriesStatement createAlignedTimeSeriesStatement) {
    for (int i = 0; i < ctx.nodeNameWithoutWildcard().size(); i++) {
      createAlignedTimeSeriesStatement.addMeasurement(
          parseNodeNameWithoutWildCard(ctx.nodeNameWithoutWildcard(i)));
      parseAttributeClausesForCreateAlignedTimeSeries(
          ctx.attributeClauses(i), createAlignedTimeSeriesStatement);
    }
  }

  public void parseAttributeClausesForCreateNonAlignedTimeSeries(
      IoTDBSqlParser.AttributeClausesContext ctx,
      CreateTimeSeriesStatement createTimeSeriesStatement) {
    if (ctx.aliasNodeName() != null) {
      createTimeSeriesStatement.setAlias(parseNodeName(ctx.aliasNodeName().nodeName()));
    }

    Map<String, String> props = new HashMap<>();
    TSDataType dataType = parseDataTypeAttribute(ctx);
    if (dataType != null) {
      props.put(
          IoTDBConstant.COLUMN_TIMESERIES_DATATYPE.toLowerCase(),
          dataType.toString().toLowerCase());
    }
    List<IoTDBSqlParser.AttributePairContext> attributePairs = ctx.attributePair();
    if (ctx.attributePair(0) != null) {
      for (IoTDBSqlParser.AttributePairContext attributePair : attributePairs) {
        props.put(
            parseAttributeKey(attributePair.attributeKey()).toLowerCase(),
            parseAttributeValue(attributePair.attributeValue()).toLowerCase());
      }
    }

    createTimeSeriesStatement.setProps(props);
    checkPropsInCreateTimeSeries(createTimeSeriesStatement);

    if (ctx.tagClause() != null) {
      parseTagClause(ctx.tagClause(), createTimeSeriesStatement);
    }
    if (ctx.attributeClause() != null) {
      parseAttributeClauseForTimeSeries(ctx.attributeClause(), createTimeSeriesStatement);
    }
  }

  /**
   * Check and set datatype, encoding, compressor.
   *
   * @throws SemanticException if encoding or dataType meets error handling
   */
  private void checkPropsInCreateTimeSeries(CreateTimeSeriesStatement createTimeSeriesStatement) {
    Map<String, String> props = createTimeSeriesStatement.getProps();
    if (props != null
        && props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_DATATYPE.toLowerCase())) {
      String datatypeString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_DATATYPE.toLowerCase()).toUpperCase();
      try {
        createTimeSeriesStatement.setDataType(TSDataType.valueOf(datatypeString));
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_DATATYPE.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported datatype: %s", datatypeString));
      }
    }
    if (createTimeSeriesStatement.getDataType() == null) {
      throw new SemanticException("datatype must be declared");
    }

    final IoTDBDescriptor ioTDBDescriptor = IoTDBDescriptor.getInstance();
    createTimeSeriesStatement.setEncoding(
        ioTDBDescriptor.getDefaultEncodingByType(createTimeSeriesStatement.getDataType()));
    if (props != null
        && props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase())) {
      String encodingString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase()).toUpperCase();
      try {
        createTimeSeriesStatement.setEncoding(TSEncoding.valueOf(encodingString));
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported encoding: %s", encodingString));
      }
    }

    createTimeSeriesStatement.setCompressor(
        TSFileDescriptor.getInstance().getConfig().getCompressor());
    if (props != null
        && props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase())) {
      String compressionString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase()).toUpperCase();
      try {
        createTimeSeriesStatement.setCompressor(CompressionType.valueOf(compressionString));
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(
            String.format("Unsupported compression: %s", compressionString));
      }
    } else if (props != null
        && props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase())) {
      String compressorString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase()).toUpperCase();
      try {
        createTimeSeriesStatement.setCompressor(CompressionType.valueOf(compressorString));
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported compression: %s", compressorString));
      }
    }
    createTimeSeriesStatement.setProps(props);
  }

  public void parseAttributeClausesForCreateAlignedTimeSeries(
      IoTDBSqlParser.AttributeClausesContext ctx,
      CreateAlignedTimeSeriesStatement createAlignedTimeSeriesStatement) {
    if (ctx.aliasNodeName() != null) {
      createAlignedTimeSeriesStatement.addAliasList(parseNodeName(ctx.aliasNodeName().nodeName()));
    } else {
      createAlignedTimeSeriesStatement.addAliasList(null);
    }

    TSDataType dataType = parseDataTypeAttribute(ctx);
    createAlignedTimeSeriesStatement.addDataType(dataType);

    Map<String, String> props = new HashMap<>();
    if (ctx.attributePair() != null) {
      for (int i = 0; i < ctx.attributePair().size(); i++) {
        props.put(
            parseAttributeKey(ctx.attributePair(i).attributeKey()).toLowerCase(),
            parseAttributeValue(ctx.attributePair(i).attributeValue()));
      }
    }

    TSEncoding encoding = IoTDBDescriptor.getInstance().getDefaultEncodingByType(dataType);
    if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase())) {
      String encodingString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase()).toUpperCase();
      try {
        encoding = TSEncoding.valueOf(encodingString);
        createAlignedTimeSeriesStatement.addEncoding(encoding);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("unsupported encoding: %s", encodingString));
      }
    } else {
      createAlignedTimeSeriesStatement.addEncoding(encoding);
    }

    CompressionType compressor = TSFileDescriptor.getInstance().getConfig().getCompressor();
    if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase())) {
      String compressorString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase()).toUpperCase();
      try {
        compressor = CompressionType.valueOf(compressorString);
        createAlignedTimeSeriesStatement.addCompressor(compressor);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("unsupported compressor: %s", compressorString));
      }
    } else if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase())) {
      String compressionString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase()).toUpperCase();
      try {
        compressor = CompressionType.valueOf(compressionString);
        createAlignedTimeSeriesStatement.addCompressor(compressor);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(
            String.format("unsupported compression: %s", compressionString));
      }
    } else {
      createAlignedTimeSeriesStatement.addCompressor(compressor);
    }

    if (props.size() > 0) {
      throw new SemanticException("create aligned timeseries: property is not supported yet.");
    }

    if (ctx.tagClause() != null) {
      parseTagClause(ctx.tagClause(), createAlignedTimeSeriesStatement);
    } else {
      createAlignedTimeSeriesStatement.addTagsList(null);
    }

    if (ctx.attributeClause() != null) {
      parseAttributeClauseForTimeSeries(ctx.attributeClause(), createAlignedTimeSeriesStatement);
    } else {
      createAlignedTimeSeriesStatement.addAttributesList(null);
    }
  }

  // Tag & Property & Attribute

  public void parseTagClause(IoTDBSqlParser.TagClauseContext ctx, Statement statement) {
    Map<String, String> tags = extractMap(ctx.attributePair(), ctx.attributePair(0));
    if (statement instanceof CreateTimeSeriesStatement) {
      ((CreateTimeSeriesStatement) statement).setTags(tags);
    } else if (statement instanceof CreateAlignedTimeSeriesStatement) {
      ((CreateAlignedTimeSeriesStatement) statement).addTagsList(tags);
    } else if (statement instanceof AlterTimeSeriesStatement) {
      ((AlterTimeSeriesStatement) statement).setTagsMap(tags);
    }
  }

  public void parseAttributeClauseForTimeSeries(
      IoTDBSqlParser.AttributeClauseContext ctx, Statement statement) {
    Map<String, String> attributes = extractMap(ctx.attributePair(), ctx.attributePair(0));
    if (statement instanceof CreateTimeSeriesStatement) {
      ((CreateTimeSeriesStatement) statement).setAttributes(attributes);
    } else if (statement instanceof CreateAlignedTimeSeriesStatement) {
      ((CreateAlignedTimeSeriesStatement) statement).addAttributesList(attributes);
    } else if (statement instanceof AlterTimeSeriesStatement) {
      ((AlterTimeSeriesStatement) statement).setAttributesMap(attributes);
    }
  }

  // Alter Timeseries ========================================================================

  @Override
  public Statement visitAlterTimeseries(IoTDBSqlParser.AlterTimeseriesContext ctx) {
    AlterTimeSeriesStatement alterTimeSeriesStatement = new AlterTimeSeriesStatement();
    alterTimeSeriesStatement.setPath(parseFullPath(ctx.fullPath()));
    parseAlterClause(ctx.alterClause(), alterTimeSeriesStatement);
    return alterTimeSeriesStatement;
  }

  private void parseAlterClause(
      IoTDBSqlParser.AlterClauseContext ctx, AlterTimeSeriesStatement alterTimeSeriesStatement) {
    Map<String, String> alterMap = new HashMap<>();
    // Rename
    if (ctx.RENAME() != null) {
      alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.RENAME);
      alterMap.put(parseAttributeKey(ctx.beforeName), parseAttributeKey(ctx.currentName));
    } else if (ctx.SET() != null) {
      // Set
      alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.SET);
      setMap(ctx, alterMap);
    } else if (ctx.DROP() != null) {
      // Drop
      alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.DROP);
      for (int i = 0; i < ctx.attributeKey().size(); i++) {
        alterMap.put(parseAttributeKey(ctx.attributeKey().get(i)), null);
      }
    } else if (ctx.TAGS() != null) {
      // Add tag
      alterTimeSeriesStatement.setAlterType((AlterTimeSeriesStatement.AlterType.ADD_TAGS));
      setMap(ctx, alterMap);
    } else if (ctx.ATTRIBUTES() != null) {
      // Add attribute
      alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.ADD_ATTRIBUTES);
      setMap(ctx, alterMap);
    } else {
      // Upsert
      alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.UPSERT);
      if (ctx.aliasClause() != null) {
        parseAliasClause(ctx.aliasClause(), alterTimeSeriesStatement);
      }
      if (ctx.tagClause() != null) {
        parseTagClause(ctx.tagClause(), alterTimeSeriesStatement);
      }
      if (ctx.attributeClause() != null) {
        parseAttributeClauseForTimeSeries(ctx.attributeClause(), alterTimeSeriesStatement);
      }
    }
    alterTimeSeriesStatement.setAlterMap(alterMap);
  }

  public void parseAliasClause(
      IoTDBSqlParser.AliasClauseContext ctx, AlterTimeSeriesStatement alterTimeSeriesStatement) {
    if (alterTimeSeriesStatement != null && ctx.ALIAS() != null) {
      alterTimeSeriesStatement.setAlias(parseAliasNode(ctx.alias()));
    }
  }

  // Drop Timeseries ======================================================================

  @Override
  public Statement visitDropTimeseries(IoTDBSqlParser.DropTimeseriesContext ctx) {
    DeleteTimeSeriesStatement deleteTimeSeriesStatement = new DeleteTimeSeriesStatement();
    List<PartialPath> partialPaths = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
      partialPaths.add(parsePrefixPath(prefixPathContext));
    }
    deleteTimeSeriesStatement.setPathPatternList(partialPaths);
    return deleteTimeSeriesStatement;
  }

  // Show Timeseries ========================================================================

  @Override
  public Statement visitShowTimeseries(IoTDBSqlParser.ShowTimeseriesContext ctx) {
    boolean orderByHeat = ctx.LATEST() != null;
    ShowTimeSeriesStatement showTimeSeriesStatement;
    if (ctx.prefixPath() != null) {
      showTimeSeriesStatement =
          new ShowTimeSeriesStatement(parsePrefixPath(ctx.prefixPath()), orderByHeat);
    } else {
      showTimeSeriesStatement =
          new ShowTimeSeriesStatement(
              new PartialPath(SqlConstant.getSingleRootArray()), orderByHeat);
    }
    if (ctx.timeseriesWhereClause() != null) {
      SchemaFilter schemaFilter = parseTimeseriesWhereClause(ctx.timeseriesWhereClause());
      showTimeSeriesStatement.setSchemaFilter(schemaFilter);
    }
    if (ctx.rowPaginationClause() != null) {
      if (ctx.rowPaginationClause().limitClause() != null) {
        showTimeSeriesStatement.setLimit(parseLimitClause(ctx.rowPaginationClause().limitClause()));
      }
      if (ctx.rowPaginationClause().offsetClause() != null) {
        showTimeSeriesStatement.setOffset(
            parseOffsetClause(ctx.rowPaginationClause().offsetClause()));
      }
    }
    return showTimeSeriesStatement;
  }

  private SchemaFilter parseTimeseriesWhereClause(IoTDBSqlParser.TimeseriesWhereClauseContext ctx) {
    if (ctx.timeseriesContainsExpression() != null) {
      // path contains filter
      return SchemaFilterFactory.createPathContainsFilter(
          parseStringLiteral(ctx.timeseriesContainsExpression().value.getText()));
    } else if (ctx.columnEqualsExpression() != null) {
      return parseColumnEqualsExpressionContext(ctx.columnEqualsExpression());
    } else {
      // tag filter
      if (ctx.tagContainsExpression() != null) {
        return SchemaFilterFactory.createTagFilter(
            parseAttributeKey(ctx.tagContainsExpression().attributeKey()),
            parseStringLiteral(ctx.tagContainsExpression().value.getText()),
            true);
      } else {
        return SchemaFilterFactory.createTagFilter(
            parseAttributeKey(ctx.tagEqualsExpression().attributeKey()),
            parseAttributeValue(ctx.tagEqualsExpression().attributeValue()),
            false);
      }
    }
  }

  private SchemaFilter parseColumnEqualsExpressionContext(
      IoTDBSqlParser.ColumnEqualsExpressionContext ctx) {
    String column = parseAttributeKey(ctx.attributeKey());
    String value = parseAttributeValue(ctx.attributeValue());
    if (column.equalsIgnoreCase(IoTDBConstant.COLUMN_TIMESERIES_DATATYPE)) {
      try {
        TSDataType dataType = TSDataType.valueOf(value.toUpperCase());
        return SchemaFilterFactory.createDataTypeFilter(dataType);
      } catch (Exception e) {
        throw new SemanticException(String.format("unsupported datatype: %s", value));
      }
    } else {
      throw new SemanticException("unexpected filter key");
    }
  }

  // SHOW DATABASES

  @Override
  public Statement visitShowDatabases(IoTDBSqlParser.ShowDatabasesContext ctx) {
    ShowDatabaseStatement showDatabaseStatement;

    // Parse prefixPath
    if (ctx.prefixPath() != null) {
      showDatabaseStatement = new ShowDatabaseStatement(parsePrefixPath(ctx.prefixPath()));
    } else {
      showDatabaseStatement =
          new ShowDatabaseStatement(new PartialPath(SqlConstant.getSingleRootArray()));
    }

    // Is detailed
    showDatabaseStatement.setDetailed(ctx.DETAILS() != null);

    return showDatabaseStatement;
  }

  // Show Devices ========================================================================

  @Override
  public Statement visitShowDevices(IoTDBSqlParser.ShowDevicesContext ctx) {
    ShowDevicesStatement showDevicesStatement;
    if (ctx.prefixPath() != null) {
      showDevicesStatement = new ShowDevicesStatement(parsePrefixPath(ctx.prefixPath()));
    } else {
      showDevicesStatement =
          new ShowDevicesStatement(new PartialPath(SqlConstant.getSingleRootArray()));
    }
    if (ctx.devicesWhereClause() != null) {
      showDevicesStatement.setSchemaFilter(parseDevicesWhereClause(ctx.devicesWhereClause()));
    }

    if (ctx.rowPaginationClause() != null) {
      if (ctx.rowPaginationClause().limitClause() != null) {
        showDevicesStatement.setLimit(parseLimitClause(ctx.rowPaginationClause().limitClause()));
      }
      if (ctx.rowPaginationClause().offsetClause() != null) {
        showDevicesStatement.setOffset(parseOffsetClause(ctx.rowPaginationClause().offsetClause()));
      }
    }
    // show devices with database
    if (ctx.WITH() != null) {
      showDevicesStatement.setSgCol(true);
    }
    return showDevicesStatement;
  }

  private SchemaFilter parseDevicesWhereClause(IoTDBSqlParser.DevicesWhereClauseContext ctx) {
    // path contains filter
    if (ctx.deviceContainsExpression() != null) {
      return SchemaFilterFactory.createPathContainsFilter(
          parseStringLiteral(ctx.deviceContainsExpression().value.getText()));
    } else {
      String templateName = null;
      boolean isEqual = true;
      if (ctx.templateEqualExpression().operator_is() != null) {
        if (ctx.templateEqualExpression().operator_not() != null) {
          isEqual = false;
        }
      } else {
        templateName = parseStringLiteral(ctx.templateEqualExpression().templateName.getText());
        if (ctx.templateEqualExpression().OPERATOR_NEQ() != null) {
          isEqual = false;
        }
      }
      return SchemaFilterFactory.createTemplateNameFilter(templateName, isEqual);
    }
  }

  // Count Devices ========================================================================

  @Override
  public Statement visitCountDevices(CountDevicesContext ctx) {
    PartialPath path;
    if (ctx.prefixPath() != null) {
      path = parsePrefixPath(ctx.prefixPath());
    } else {
      path = new PartialPath(SqlConstant.getSingleRootArray());
    }
    return new CountDevicesStatement(path);
  }

  // Count TimeSeries ========================================================================
  @Override
  public Statement visitCountTimeseries(CountTimeseriesContext ctx) {
    Statement statement;
    PartialPath path;
    if (ctx.prefixPath() != null) {
      path = parsePrefixPath(ctx.prefixPath());
    } else {
      path = new PartialPath(SqlConstant.getSingleRootArray());
    }
    if (ctx.INTEGER_LITERAL() != null) {
      int level = Integer.parseInt(ctx.INTEGER_LITERAL().getText());
      statement = new CountLevelTimeSeriesStatement(path, level);
    } else {
      statement = new CountTimeSeriesStatement(path);
    }
    if (ctx.timeseriesWhereClause() != null) {
      SchemaFilter schemaFilter = parseTimeseriesWhereClause(ctx.timeseriesWhereClause());
      if (statement instanceof CountTimeSeriesStatement) {
        ((CountTimeSeriesStatement) statement).setSchemaFilter(schemaFilter);
      } else if (statement instanceof CountLevelTimeSeriesStatement) {
        ((CountLevelTimeSeriesStatement) statement).setSchemaFilter(schemaFilter);
      }
    }
    return statement;
  }

  // Count Nodes ========================================================================
  @Override
  public Statement visitCountNodes(CountNodesContext ctx) {
    PartialPath path;
    if (ctx.prefixPath() != null) {
      path = parsePrefixPath(ctx.prefixPath());
    } else {
      path = new PartialPath(SqlConstant.getSingleRootArray());
    }
    int level = Integer.parseInt(ctx.INTEGER_LITERAL().getText());
    return new CountNodesStatement(path, level);
  }

  // Count StorageGroup ========================================================================
  @Override
  public Statement visitCountDatabases(CountDatabasesContext ctx) {
    PartialPath path;
    if (ctx.prefixPath() != null) {
      path = parsePrefixPath(ctx.prefixPath());
    } else {
      path = new PartialPath(SqlConstant.getSingleRootArray());
    }
    return new CountDatabaseStatement(path);
  }

  // Show version
  @Override
  public Statement visitShowVersion(IoTDBSqlParser.ShowVersionContext ctx) {
    return new ShowVersionStatement();
  }

  // Create Function
  @Override
  public Statement visitCreateFunction(CreateFunctionContext ctx) {
    if (ctx.uriClause() == null) {
      return new CreateFunctionStatement(
          parseIdentifier(ctx.udfName.getText()),
          parseStringLiteral(ctx.className.getText()),
          false);
    } else {
      String uriString = parseAndValidateURI(ctx.uriClause());
      return new CreateFunctionStatement(
          parseIdentifier(ctx.udfName.getText()),
          parseStringLiteral(ctx.className.getText()),
          true,
          uriString);
    }
  }

  private String parseAndValidateURI(IoTDBSqlParser.UriClauseContext ctx) {
    String uriString = parseStringLiteral(ctx.uri().getText());
    try {
      new URI(uriString);
    } catch (URISyntaxException e) {
      throw new SemanticException(String.format("Invalid URI: %s", uriString));
    }
    return uriString;
  }

  // Drop Function
  @Override
  public Statement visitDropFunction(DropFunctionContext ctx) {
    return new DropFunctionStatement(parseIdentifier(ctx.udfName.getText()));
  }

  // Show Functions
  @Override
  public Statement visitShowFunctions(ShowFunctionsContext ctx) {
    return new ShowFunctionsStatement();
  }

  // Create Trigger =====================================================================
  @Override
  public Statement visitCreateTrigger(IoTDBSqlParser.CreateTriggerContext ctx) {
    if (ctx.triggerEventClause().DELETE() != null) {
      throw new SemanticException("Trigger does not support DELETE as TRIGGER_EVENT for now.");
    }
    if (ctx.triggerType() == null) {
      throw new SemanticException("Please specify trigger type: STATELESS or STATEFUL.");
    }
    Map<String, String> attributes = new HashMap<>();
    if (ctx.triggerAttributeClause() != null) {
      for (IoTDBSqlParser.TriggerAttributeContext triggerAttributeContext :
          ctx.triggerAttributeClause().triggerAttribute()) {
        attributes.put(
            parseAttributeKey(triggerAttributeContext.key),
            parseAttributeValue(triggerAttributeContext.value));
      }
    }
    if (ctx.uriClause() == null) {
      return new CreateTriggerStatement(
          parseIdentifier(ctx.triggerName.getText()),
          parseStringLiteral(ctx.className.getText()),
          "",
          false,
          ctx.triggerEventClause().BEFORE() != null
              ? TriggerEvent.BEFORE_INSERT
              : TriggerEvent.AFTER_INSERT,
          ctx.triggerType().STATELESS() != null ? TriggerType.STATELESS : TriggerType.STATEFUL,
          parsePrefixPath(ctx.prefixPath()),
          attributes);
    } else {
      String uriString = parseAndValidateURI(ctx.uriClause());
      return new CreateTriggerStatement(
          parseIdentifier(ctx.triggerName.getText()),
          parseStringLiteral(ctx.className.getText()),
          uriString,
          true,
          ctx.triggerEventClause().BEFORE() != null
              ? TriggerEvent.BEFORE_INSERT
              : TriggerEvent.AFTER_INSERT,
          ctx.triggerType().STATELESS() != null ? TriggerType.STATELESS : TriggerType.STATEFUL,
          parsePrefixPath(ctx.prefixPath()),
          attributes);
    }
  }

  // Drop Trigger =====================================================================
  @Override
  public Statement visitDropTrigger(IoTDBSqlParser.DropTriggerContext ctx) {
    return new DropTriggerStatement(parseIdentifier(ctx.triggerName.getText()));
  }

  // Show Trigger =====================================================================
  @Override
  public Statement visitShowTriggers(IoTDBSqlParser.ShowTriggersContext ctx) {
    return new ShowTriggersStatement();
  }

  // Create PipePlugin =====================================================================
  @Override
  public Statement visitCreatePipePlugin(IoTDBSqlParser.CreatePipePluginContext ctx) {
    return new CreatePipePluginStatement(
        ctx.pluginName.getText(),
        parseStringLiteral(ctx.className.getText()),
        parseAndValidateURI(ctx.uriClause()));
  }

  // Drop PipePlugin =====================================================================
  @Override
  public Statement visitDropPipePlugin(IoTDBSqlParser.DropPipePluginContext ctx) {
    return new DropPipePluginStatement(ctx.pluginName.getText());
  }

  // Show PipePlugins =====================================================================
  @Override
  public Statement visitShowPipePlugins(IoTDBSqlParser.ShowPipePluginsContext ctx) {
    return new ShowPipePluginsStatement();
  }

  // Show Child Paths =====================================================================
  @Override
  public Statement visitShowChildPaths(IoTDBSqlParser.ShowChildPathsContext ctx) {
    if (ctx.prefixPath() != null) {
      return new ShowChildPathsStatement(parsePrefixPath(ctx.prefixPath()));
    } else {
      return new ShowChildPathsStatement(new PartialPath(SqlConstant.getSingleRootArray()));
    }
  }

  // Show Child Nodes =====================================================================
  @Override
  public Statement visitShowChildNodes(IoTDBSqlParser.ShowChildNodesContext ctx) {
    if (ctx.prefixPath() != null) {
      return new ShowChildNodesStatement(parsePrefixPath(ctx.prefixPath()));
    } else {
      return new ShowChildNodesStatement(new PartialPath(SqlConstant.getSingleRootArray()));
    }
  }

  // Create CQ =====================================================================
  @Override
  public Statement visitCreateContinuousQuery(IoTDBSqlParser.CreateContinuousQueryContext ctx) {
    CreateContinuousQueryStatement statement = new CreateContinuousQueryStatement();

    statement.setCqId(parseIdentifier(ctx.cqId.getText()));

    QueryStatement queryBodyStatement =
        (QueryStatement) visitSelectStatement(ctx.selectStatement());
    queryBodyStatement.setCqQueryBody(true);
    statement.setQueryBodyStatement(queryBodyStatement);

    if (ctx.resampleClause() != null) {
      parseResampleClause(ctx.resampleClause(), statement);
    } else {
      QueryStatement queryStatement = statement.getQueryBodyStatement();
      if (!queryStatement.isGroupByTime()) {
        throw new SemanticException(
            "CQ: At least one of the parameters `every_interval` and `group_by_interval` needs to be specified.");
      }

      long interval =
          queryStatement.getGroupByTimeComponent().getInterval().getTotalDuration(currPrecision);
      statement.setEveryInterval(interval);
      statement.setStartTimeOffset(interval);
    }

    if (ctx.timeoutPolicyClause() != null) {
      parseTimeoutPolicyClause(ctx.timeoutPolicyClause(), statement);
    }

    return statement;
  }

  private void parseResampleClause(
      IoTDBSqlParser.ResampleClauseContext ctx, CreateContinuousQueryStatement statement) {
    if (ctx.EVERY() != null) {
      statement.setEveryInterval(
          DateTimeUtils.convertDurationStrToLong(ctx.everyInterval.getText()));
    } else {
      QueryStatement queryStatement = statement.getQueryBodyStatement();
      if (!queryStatement.isGroupByTime()) {
        throw new SemanticException(
            "CQ: At least one of the parameters `every_interval` and `group_by_interval` needs to be specified.");
      }
      statement.setEveryInterval(
          queryStatement.getGroupByTimeComponent().getInterval().getTotalDuration(currPrecision));
    }

    if (ctx.BOUNDARY() != null) {
      statement.setBoundaryTime(
          parseTimeValue(ctx.boundaryTime, CommonDateTimeUtils.currentTime()));
    }

    if (ctx.RANGE() != null) {
      statement.setStartTimeOffset(
          DateTimeUtils.convertDurationStrToLong(ctx.startTimeOffset.getText()));
      if (ctx.endTimeOffset != null) {
        statement.setEndTimeOffset(
            DateTimeUtils.convertDurationStrToLong(ctx.endTimeOffset.getText()));
      }
    } else {
      statement.setStartTimeOffset(statement.getEveryInterval());
    }
  }

  private void parseTimeoutPolicyClause(
      IoTDBSqlParser.TimeoutPolicyClauseContext ctx, CreateContinuousQueryStatement statement) {
    if (ctx.DISCARD() != null) {
      statement.setTimeoutPolicy(TimeoutPolicy.DISCARD);
    }
  }

  // Drop CQ =====================================================================
  @Override
  public Statement visitDropContinuousQuery(IoTDBSqlParser.DropContinuousQueryContext ctx) {
    return new DropContinuousQueryStatement(parseIdentifier(ctx.cqId.getText()));
  }

  // Show CQs =====================================================================
  @Override
  public Statement visitShowContinuousQueries(IoTDBSqlParser.ShowContinuousQueriesContext ctx) {
    return new ShowContinuousQueriesStatement();
  }

  // Create Logical View
  @Override
  public Statement visitCreateLogicalView(IoTDBSqlParser.CreateLogicalViewContext ctx) {
    CreateLogicalViewStatement createLogicalViewStatement = new CreateLogicalViewStatement();
    // parse target
    parseViewTargetPaths(
        ctx.viewTargetPaths(),
        createLogicalViewStatement::setTargetFullPaths,
        createLogicalViewStatement::setTargetPathsGroup,
        createLogicalViewStatement::setTargetIntoItem);
    // parse source
    parseViewSourcePaths(
        ctx.viewSourcePaths(),
        createLogicalViewStatement::setSourceFullPaths,
        createLogicalViewStatement::setSourcePathsGroup,
        createLogicalViewStatement::setSourceQueryStatement);

    return createLogicalViewStatement;
  }

  @Override
  public Statement visitDropLogicalView(IoTDBSqlParser.DropLogicalViewContext ctx) {
    DeleteLogicalViewStatement deleteLogicalViewStatement = new DeleteLogicalViewStatement();
    List<PartialPath> partialPaths = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
      partialPaths.add(parsePrefixPath(prefixPathContext));
    }
    deleteLogicalViewStatement.setPathPatternList(partialPaths);
    return deleteLogicalViewStatement;
  }

  @Override
  public Statement visitShowLogicalView(IoTDBSqlParser.ShowLogicalViewContext ctx) {
    ShowLogicalViewStatement showLogicalViewStatement;
    if (ctx.prefixPath() != null) {
      showLogicalViewStatement = new ShowLogicalViewStatement(parsePrefixPath(ctx.prefixPath()));
    } else {
      showLogicalViewStatement =
          new ShowLogicalViewStatement(new PartialPath(SqlConstant.getSingleRootArray()));
    }
    if (ctx.timeseriesWhereClause() != null) {
      SchemaFilter schemaFilter = parseTimeseriesWhereClause(ctx.timeseriesWhereClause());
      showLogicalViewStatement.setSchemaFilter(schemaFilter);
    }
    if (ctx.rowPaginationClause() != null) {
      if (ctx.rowPaginationClause().limitClause() != null) {
        showLogicalViewStatement.setLimit(
            parseLimitClause(ctx.rowPaginationClause().limitClause()));
      }
      if (ctx.rowPaginationClause().offsetClause() != null) {
        showLogicalViewStatement.setOffset(
            parseOffsetClause(ctx.rowPaginationClause().offsetClause()));
      }
    }
    return showLogicalViewStatement;
  }

  @Override
  public Statement visitRenameLogicalView(IoTDBSqlParser.RenameLogicalViewContext ctx) {
    throw new SemanticException("Renaming view is not supported.");
  }

  @Override
  public Statement visitAlterLogicalView(IoTDBSqlParser.AlterLogicalViewContext ctx) {
    if (ctx.alterClause() == null) {
      AlterLogicalViewStatement alterLogicalViewStatement = new AlterLogicalViewStatement();
      // parse target
      parseViewTargetPaths(
          ctx.viewTargetPaths(),
          alterLogicalViewStatement::setTargetFullPaths,
          alterLogicalViewStatement::setTargetPathsGroup,
          intoItem -> {
            if (intoItem != null) {
              throw new SemanticException(
                  "Can not use char '$' or into item in alter view statement.");
            }
          });
      // parse source
      parseViewSourcePaths(
          ctx.viewSourcePaths(),
          alterLogicalViewStatement::setSourceFullPaths,
          alterLogicalViewStatement::setSourcePathsGroup,
          alterLogicalViewStatement::setSourceQueryStatement);

      return alterLogicalViewStatement;
    } else {
      AlterTimeSeriesStatement alterTimeSeriesStatement = new AlterTimeSeriesStatement(true);
      alterTimeSeriesStatement.setPath(parseFullPath(ctx.fullPath()));
      parseAlterClause(ctx.alterClause(), alterTimeSeriesStatement);
      if (alterTimeSeriesStatement.getAlias() != null) {
        throw new SemanticException("View doesn't support alias.");
      }
      return alterTimeSeriesStatement;
    }
  }

  // parse suffix paths in logical view with into item
  private PartialPath parseViewPrefixPathWithInto(IoTDBSqlParser.PrefixPathContext ctx) {
    List<IoTDBSqlParser.NodeNameContext> nodeNames = ctx.nodeName();
    String[] path = new String[nodeNames.size() + 1];
    path[0] = ctx.ROOT().getText();
    for (int i = 0; i < nodeNames.size(); i++) {
      path[i + 1] = parseNodeStringInIntoPath(nodeNames.get(i).getText());
    }
    return new PartialPath(path);
  }

  private PartialPath parseViewSuffixPatWithInto(IoTDBSqlParser.ViewSuffixPathsContext ctx) {
    List<IoTDBSqlParser.NodeNameWithoutWildcardContext> nodeNamesWithoutStar =
        ctx.nodeNameWithoutWildcard();
    String[] nodeList = new String[nodeNamesWithoutStar.size()];
    for (int i = 0; i < nodeNamesWithoutStar.size(); i++) {
      nodeList[i] = parseNodeStringInIntoPath(nodeNamesWithoutStar.get(i).getText());
    }
    return new PartialPath(nodeList);
  }

  private PartialPath parseViewSuffixPath(IoTDBSqlParser.ViewSuffixPathsContext ctx) {
    List<IoTDBSqlParser.NodeNameWithoutWildcardContext> nodeNamesWithoutStar =
        ctx.nodeNameWithoutWildcard();
    String[] nodeList = new String[nodeNamesWithoutStar.size()];
    for (int i = 0; i < nodeNamesWithoutStar.size(); i++) {
      nodeList[i] = parseNodeNameWithoutWildCard(nodeNamesWithoutStar.get(i));
    }
    return new PartialPath(nodeList);
  }

  // parse target paths in CreateLogicalView statement
  private void parseViewTargetPaths(
      IoTDBSqlParser.ViewTargetPathsContext ctx,
      Consumer<List<PartialPath>> setTargetFullPaths,
      BiConsumer<PartialPath, List<PartialPath>> setTargetPathsGroup,
      Consumer<IntoItem> setTargetIntoItem) {
    // full paths
    if (ctx.fullPath() != null && !ctx.fullPath().isEmpty()) {
      List<IoTDBSqlParser.FullPathContext> fullPathContextList = ctx.fullPath();
      List<PartialPath> pathList = new ArrayList<>();
      for (IoTDBSqlParser.FullPathContext pathContext : fullPathContextList) {
        pathList.add(parseFullPath(pathContext));
      }
      setTargetFullPaths.accept(pathList);
    }
    // prefix path and suffix paths
    if (ctx.prefixPath() != null
        && ctx.viewSuffixPaths() != null
        && !ctx.viewSuffixPaths().isEmpty()) {
      IoTDBSqlParser.PrefixPathContext prefixPathContext = ctx.prefixPath();
      List<IoTDBSqlParser.ViewSuffixPathsContext> suffixPathContextList = ctx.viewSuffixPaths();
      List<PartialPath> suffixPathList = new ArrayList<>();
      PartialPath prefixPath = null;
      boolean isMultipleCreating = false;
      try {
        prefixPath = parsePrefixPath(prefixPathContext);
        for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : suffixPathContextList) {
          suffixPathList.add(parseViewSuffixPath(suffixPathContext));
        }
      } catch (SemanticException e) {
        // there is '$', '{', '}' in this statement
        isMultipleCreating = true;
        suffixPathList.clear();
      }
      if (!isMultipleCreating) {
        setTargetPathsGroup.accept(prefixPath, suffixPathList);
      } else {
        prefixPath = parseViewPrefixPathWithInto(prefixPathContext);
        for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : suffixPathContextList) {
          suffixPathList.add(parseViewSuffixPatWithInto(suffixPathContext));
        }
        List<String> intoMeasurementList = new ArrayList<>();
        for (PartialPath path : suffixPathList) {
          intoMeasurementList.add(path.toString());
        }
        IntoItem intoItem = new IntoItem(prefixPath, intoMeasurementList, false);
        setTargetIntoItem.accept(intoItem);
      }
    }
  }

  // parse source paths in CreateLogicalView statement
  private void parseViewSourcePaths(
      IoTDBSqlParser.ViewSourcePathsContext ctx,
      Consumer<List<PartialPath>> setSourceFullPaths,
      BiConsumer<PartialPath, List<PartialPath>> setSourcePathsGroup,
      Consumer<QueryStatement> setSourceQueryStatement) {
    // full paths
    if (ctx.fullPath() != null && !ctx.fullPath().isEmpty()) {
      List<IoTDBSqlParser.FullPathContext> fullPathContextList = ctx.fullPath();
      List<PartialPath> pathList = new ArrayList<>();
      for (IoTDBSqlParser.FullPathContext pathContext : fullPathContextList) {
        pathList.add(parseFullPath(pathContext));
      }
      setSourceFullPaths.accept(pathList);
    }
    // prefix path and suffix paths
    if (ctx.prefixPath() != null
        && ctx.viewSuffixPaths() != null
        && !ctx.viewSuffixPaths().isEmpty()) {
      IoTDBSqlParser.PrefixPathContext prefixPathContext = ctx.prefixPath();
      PartialPath prefixPath = parsePrefixPath(prefixPathContext);
      List<IoTDBSqlParser.ViewSuffixPathsContext> suffixPathContextList = ctx.viewSuffixPaths();
      List<PartialPath> suffixPathList = new ArrayList<>();
      for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : suffixPathContextList) {
        suffixPathList.add(parseViewSuffixPath(suffixPathContext));
      }
      setSourcePathsGroup.accept(prefixPath, suffixPathList);
    }
    if (ctx.selectClause() != null && ctx.fromClause() != null) {
      QueryStatement queryStatement = new QueryStatement();
      queryStatement.setSelectComponent(parseSelectClause(ctx.selectClause(), queryStatement));
      queryStatement.setFromComponent(parseFromClause(ctx.fromClause()));
      setSourceQueryStatement.accept(queryStatement);
    }
  }

  /** Data Manipulation Language (DML). */

  // Select Statement ========================================================================
  @Override
  public Statement visitSelectStatement(IoTDBSqlParser.SelectStatementContext ctx) {
    QueryStatement queryStatement = new QueryStatement();

    // parse SELECT & FROM
    queryStatement.setSelectComponent(parseSelectClause(ctx.selectClause(), queryStatement));
    queryStatement.setFromComponent(parseFromClause(ctx.fromClause()));

    // parse INTO
    if (ctx.intoClause() != null) {
      queryStatement.setIntoComponent(parseIntoClause(ctx.intoClause()));
    }

    // parse WHERE
    if (ctx.whereClause() != null) {
      queryStatement.setWhereCondition(parseWhereClause(ctx.whereClause()));
    }

    // parse GROUP BY
    if (ctx.groupByClause() != null) {
      Set<String> groupByKeys = new HashSet<>();
      List<IoTDBSqlParser.GroupByAttributeClauseContext> groupByAttributes =
          ctx.groupByClause().groupByAttributeClause();
      for (IoTDBSqlParser.GroupByAttributeClauseContext groupByAttribute : groupByAttributes) {
        if (groupByAttribute.TIME() != null || groupByAttribute.interval != null) {
          if (groupByKeys.contains("COMMON")) {
            throw new SemanticException(GROUP_BY_COMMON_ONLY_ONE_MSG);
          }

          groupByKeys.add("COMMON");
          queryStatement.setGroupByTimeComponent(parseGroupByTimeClause(groupByAttribute));
        } else if (groupByAttribute.LEVEL() != null) {
          if (groupByKeys.contains("LEVEL")) {
            throw new SemanticException("duplicated group by key: LEVEL");
          }

          groupByKeys.add("LEVEL");
          queryStatement.setGroupByLevelComponent(parseGroupByLevelClause(groupByAttribute));
        } else if (groupByAttribute.TAGS() != null) {
          if (groupByKeys.contains("TAGS")) {
            throw new SemanticException("duplicated group by key: TAGS");
          }

          groupByKeys.add("TAGS");
          queryStatement.setGroupByTagComponent(parseGroupByTagClause(groupByAttribute));
        } else if (groupByAttribute.VARIATION() != null) {
          if (groupByKeys.contains("COMMON")) {
            throw new SemanticException(GROUP_BY_COMMON_ONLY_ONE_MSG);
          }

          groupByKeys.add("COMMON");
          queryStatement.setGroupByComponent(
              parseGroupByClause(groupByAttribute, WindowType.VARIATION_WINDOW));
        } else if (groupByAttribute.CONDITION() != null) {
          if (groupByKeys.contains("COMMON")) {
            throw new SemanticException(GROUP_BY_COMMON_ONLY_ONE_MSG);
          }

          groupByKeys.add("COMMON");
          queryStatement.setGroupByComponent(
              parseGroupByClause(groupByAttribute, WindowType.CONDITION_WINDOW));
        } else if (groupByAttribute.SESSION() != null) {
          if (groupByKeys.contains("COMMON")) {
            throw new SemanticException(GROUP_BY_COMMON_ONLY_ONE_MSG);
          }

          groupByKeys.add("COMMON");
          queryStatement.setGroupByComponent(
              parseGroupByClause(groupByAttribute, WindowType.SESSION_WINDOW));
        } else if (groupByAttribute.COUNT() != null) {
          if (groupByKeys.contains("COMMON")) {
            throw new SemanticException(GROUP_BY_COMMON_ONLY_ONE_MSG);
          }

          groupByKeys.add("COMMON");
          queryStatement.setGroupByComponent(
              parseGroupByClause(groupByAttribute, WindowType.COUNT_WINDOW));

        } else {
          throw new SemanticException("Unknown GROUP BY type.");
        }
      }
    }

    // parse HAVING
    if (ctx.havingClause() != null) {
      queryStatement.setHavingCondition(parseHavingClause(ctx.havingClause()));
    }

    // parse ORDER BY
    if (ctx.orderByClause() != null) {
      queryStatement.setOrderByComponent(
          parseOrderByClause(
              ctx.orderByClause(),
              ImmutableSet.of(OrderByKey.TIME, OrderByKey.DEVICE, OrderByKey.TIMESERIES)));
    }

    // parse FILL
    if (ctx.fillClause() != null) {
      queryStatement.setFillComponent(parseFillClause(ctx.fillClause()));
    }

    // parse ALIGN BY
    if (ctx.alignByClause() != null) {
      queryStatement.setResultSetFormat(parseAlignBy(ctx.alignByClause()));
    }

    if (ctx.paginationClause() != null) {
      // parse SLIMIT & SOFFSET
      if (ctx.paginationClause().seriesPaginationClause() != null) {
        if (ctx.paginationClause().seriesPaginationClause().slimitClause() != null) {
          queryStatement.setSeriesLimit(
              parseSLimitClause(ctx.paginationClause().seriesPaginationClause().slimitClause()));
        }
        if (ctx.paginationClause().seriesPaginationClause().soffsetClause() != null) {
          queryStatement.setSeriesOffset(
              parseSOffsetClause(ctx.paginationClause().seriesPaginationClause().soffsetClause()));
        }
      }

      // parse LIMIT & OFFSET
      if (ctx.paginationClause().rowPaginationClause() != null) {
        if (ctx.paginationClause().rowPaginationClause().limitClause() != null) {
          queryStatement.setRowLimit(
              parseLimitClause(ctx.paginationClause().rowPaginationClause().limitClause()));
        }
        if (ctx.paginationClause().rowPaginationClause().offsetClause() != null) {
          queryStatement.setRowOffset(
              parseOffsetClause(ctx.paginationClause().rowPaginationClause().offsetClause()));
        }
        if (canPushDownLimitOffsetToGroupByTime(queryStatement)) {
          pushDownLimitOffsetToTimeParameter(queryStatement);
        }
      }
    }

    queryStatement.setUseWildcard(useWildcard);
    queryStatement.setLastLevelUseWildcard(lastLevelUseWildcard);
    return queryStatement;
  }

  // ---- Select Clause
  private SelectComponent parseSelectClause(
      IoTDBSqlParser.SelectClauseContext ctx, QueryStatement queryStatement) {
    SelectComponent selectComponent = new SelectComponent(zoneId);

    // parse LAST
    if (ctx.LAST() != null) {
      selectComponent.setHasLast(true);
    }

    // parse resultColumn
    Map<String, Expression> aliasToColumnMap = new HashMap<>();
    for (IoTDBSqlParser.ResultColumnContext resultColumnContext : ctx.resultColumn()) {
      ResultColumn resultColumn = parseResultColumn(resultColumnContext);
      // __endTime shouldn't be included in resultColumns
      if (resultColumn.getExpression().getExpressionString().equals(ColumnHeaderConstant.ENDTIME)) {
        queryStatement.setOutputEndTime(true);
        continue;
      }
      if (resultColumn.hasAlias()) {
        String alias = resultColumn.getAlias();
        if (aliasToColumnMap.containsKey(alias)) {
          throw new SemanticException("duplicate alias in select clause");
        }
        aliasToColumnMap.put(alias, resultColumn.getExpression());
      }
      selectComponent.addResultColumn(resultColumn);
    }
    selectComponent.setAliasToColumnMap(aliasToColumnMap);

    return selectComponent;
  }

  private ResultColumn parseResultColumn(IoTDBSqlParser.ResultColumnContext resultColumnContext) {
    Expression expression = parseExpression(resultColumnContext.expression(), false);
    if (expression.isConstantOperand()) {
      throw new SemanticException("Constant operand is not allowed: " + expression);
    }
    String alias = null;
    if (resultColumnContext.AS() != null) {
      alias = parseAlias(resultColumnContext.alias());
    }
    ResultColumn.ColumnType columnType =
        ExpressionAnalyzer.identifyOutputColumnType(expression, true);
    return new ResultColumn(expression, alias, columnType);
  }

  // ---- From Clause
  private FromComponent parseFromClause(IoTDBSqlParser.FromClauseContext ctx) {
    FromComponent fromComponent = new FromComponent();
    List<IoTDBSqlParser.PrefixPathContext> prefixFromPaths = ctx.prefixPath();
    for (IoTDBSqlParser.PrefixPathContext prefixFromPath : prefixFromPaths) {
      PartialPath path = parsePrefixPath(prefixFromPath);
      fromComponent.addPrefixPath(path);
    }
    return fromComponent;
  }

  // ---- Into Clause
  private IntoComponent parseIntoClause(IoTDBSqlParser.IntoClauseContext ctx) {
    List<IntoItem> intoItems = new ArrayList<>();
    for (IoTDBSqlParser.IntoItemContext intoItemContext : ctx.intoItem()) {
      intoItems.add(parseIntoItem(intoItemContext));
    }
    return new IntoComponent(intoItems);
  }

  private IntoItem parseIntoItem(IoTDBSqlParser.IntoItemContext intoItemContext) {
    boolean isAligned = intoItemContext.ALIGNED() != null;
    PartialPath intoDevice = parseIntoPath(intoItemContext.intoPath());
    List<String> intoMeasurements =
        intoItemContext.nodeNameInIntoPath().stream()
            .map(this::parseNodeNameInIntoPath)
            .collect(Collectors.toList());
    return new IntoItem(intoDevice, intoMeasurements, isAligned);
  }

  private PartialPath parseIntoPath(IoTDBSqlParser.IntoPathContext intoPathContext) {
    if (intoPathContext instanceof IoTDBSqlParser.FullPathInIntoPathContext) {
      return parseFullPathInIntoPath((IoTDBSqlParser.FullPathInIntoPathContext) intoPathContext);
    } else {
      List<IoTDBSqlParser.NodeNameInIntoPathContext> nodeNames =
          ((IoTDBSqlParser.SuffixPathInIntoPathContext) intoPathContext).nodeNameInIntoPath();
      String[] path = new String[nodeNames.size()];
      for (int i = 0; i < nodeNames.size(); i++) {
        path[i] = parseNodeNameInIntoPath(nodeNames.get(i));
      }
      return new PartialPath(path);
    }
  }

  // ---- Where Clause
  private WhereCondition parseWhereClause(IoTDBSqlParser.WhereClauseContext ctx) {
    Expression predicate = parseExpression(ctx.expression(), true);
    return new WhereCondition(predicate);
  }

  // ---- Group By Clause
  private GroupByTimeComponent parseGroupByTimeClause(
      IoTDBSqlParser.GroupByAttributeClauseContext ctx) {
    GroupByTimeComponent groupByTimeComponent = new GroupByTimeComponent();

    // Parse time range
    if (ctx.timeRange() != null) {
      parseTimeRangeForGroupByTime(ctx.timeRange(), groupByTimeComponent);
      groupByTimeComponent.setLeftCRightO(ctx.timeRange().LS_BRACKET() != null);
    }

    // Parse time interval
    groupByTimeComponent.setInterval(DateTimeUtils.constructTimeDuration(ctx.interval.getText()));
    groupByTimeComponent.setOriginalInterval(ctx.interval.getText());
    if (groupByTimeComponent.getInterval().monthDuration == 0
        && groupByTimeComponent.getInterval().nonMonthDuration == 0) {
      throw new SemanticException(
          "The second parameter time interval should be a positive integer.");
    }

    // parse sliding step
    if (ctx.step != null) {
      groupByTimeComponent.setSlidingStep(DateTimeUtils.constructTimeDuration(ctx.step.getText()));
      groupByTimeComponent.setOriginalSlidingStep(ctx.step.getText());
    } else {
      groupByTimeComponent.setSlidingStep(groupByTimeComponent.getInterval());
      groupByTimeComponent.setOriginalSlidingStep(groupByTimeComponent.getOriginalInterval());
    }
    TimeDuration slidingStep = groupByTimeComponent.getSlidingStep();
    if (slidingStep.containsMonth()
        && Math.ceil(
                ((groupByTimeComponent.getEndTime() - groupByTimeComponent.getStartTime())
                    / (double) slidingStep.getMinTotalDuration(currPrecision)))
            >= 10000) {
      throw new SemanticException("The time windows may exceed 10000, please ensure your input.");
    }
    if (groupByTimeComponent.getSlidingStep().monthDuration == 0
        && groupByTimeComponent.getSlidingStep().nonMonthDuration == 0) {
      throw new SemanticException(
          "The third parameter time slidingStep should be a positive integer.");
    }
    return groupByTimeComponent;
  }

  /**
   * Parse time range (startTime and endTime) in group by time.
   *
   * @throws SemanticException if startTime is larger or equals to endTime in timeRange
   */
  private void parseTimeRangeForGroupByTime(
      IoTDBSqlParser.TimeRangeContext timeRange, GroupByTimeComponent groupByClauseComponent) {
    long currentTime = CommonDateTimeUtils.currentTime();
    long startTime = parseTimeValue(timeRange.timeValue(0), currentTime);
    long endTime = parseTimeValue(timeRange.timeValue(1), currentTime);
    groupByClauseComponent.setStartTime(startTime);
    groupByClauseComponent.setEndTime(endTime);
    if (startTime >= endTime) {
      throw new SemanticException("Start time should be smaller than endTime in GroupBy");
    }
  }

  private GroupByComponent parseGroupByClause(
      GroupByAttributeClauseContext ctx, WindowType windowType) {

    boolean ignoringNull = true;
    if (ctx.attributePair() != null
        && !ctx.attributePair().isEmpty()
        && ctx.attributePair().key.getText().equalsIgnoreCase(IGNORENULL)) {
      ignoringNull = Boolean.parseBoolean(ctx.attributePair().value.getText());
    }
    List<ExpressionContext> expressions = ctx.expression();
    if (windowType == WindowType.VARIATION_WINDOW) {
      ExpressionContext expressionContext = expressions.get(0);
      GroupByVariationComponent groupByVariationComponent = new GroupByVariationComponent();
      Expression expression = parseExpression(expressionContext, true);
      if (expression.isConstantOperand()) {
        throw new SemanticException(
            String.format(
                "Constant operand [%s] is not allowed in group by variation, there should be an expression",
                expression.getExpressionString()));
      }
      groupByVariationComponent.setControlColumnExpression(expression);
      groupByVariationComponent.setDelta(
          ctx.delta == null ? 0 : Double.parseDouble(ctx.delta.getText()));
      groupByVariationComponent.setIgnoringNull(ignoringNull);
      return groupByVariationComponent;
    } else if (windowType == WindowType.CONDITION_WINDOW) {
      ExpressionContext conditionExpressionContext = expressions.get(0);
      GroupByConditionComponent groupByConditionComponent = new GroupByConditionComponent();
      groupByConditionComponent.setControlColumnExpression(
          parseExpression(conditionExpressionContext, true));
      if (expressions.size() == 2) {
        groupByConditionComponent.setKeepExpression(parseExpression(expressions.get(1), true));
      } else {
        throw new SemanticException("Keep threshold in group by condition should be set");
      }
      groupByConditionComponent.setIgnoringNull(ignoringNull);
      return groupByConditionComponent;
    } else if (windowType == WindowType.SESSION_WINDOW) {
      long interval = DateTimeUtils.convertDurationStrToLong(ctx.timeInterval.getText());
      return new GroupBySessionComponent(interval);
    } else if (windowType == WindowType.COUNT_WINDOW) {
      ExpressionContext countExpressionContext = expressions.get(0);
      long countNumber = Long.parseLong(ctx.countNumber.getText());
      GroupByCountComponent groupByCountComponent = new GroupByCountComponent(countNumber);
      Expression expression = parseExpression(countExpressionContext, true);
      if (expression.isConstantOperand()) {
        throw new SemanticException(
            String.format(
                "Constant operand [%s] is not allowed in group by count, there should be an expression",
                expression.getExpressionString()));
      }
      groupByCountComponent.setControlColumnExpression(expression);
      groupByCountComponent.setIgnoringNull(ignoringNull);
      return groupByCountComponent;
    } else {
      throw new SemanticException("Unsupported window type");
    }
  }

  private GroupByLevelComponent parseGroupByLevelClause(
      IoTDBSqlParser.GroupByAttributeClauseContext ctx) {
    GroupByLevelComponent groupByLevelComponent = new GroupByLevelComponent();
    int[] levels = new int[ctx.INTEGER_LITERAL().size()];
    for (int i = 0; i < ctx.INTEGER_LITERAL().size(); i++) {
      levels[i] = Integer.parseInt(ctx.INTEGER_LITERAL().get(i).getText());
    }
    groupByLevelComponent.setLevels(levels);
    return groupByLevelComponent;
  }

  private GroupByTagComponent parseGroupByTagClause(
      IoTDBSqlParser.GroupByAttributeClauseContext ctx) {
    Set<String> tagKeys = new LinkedHashSet<>();
    for (IdentifierContext identifierContext : ctx.identifier()) {
      String key = parseIdentifier(identifierContext.getText());
      if (tagKeys.contains(key)) {
        throw new SemanticException("duplicated key in GROUP BY TAGS: " + key);
      }
      tagKeys.add(key);
    }
    return new GroupByTagComponent(new ArrayList<>(tagKeys));
  }

  // ---- Having Clause
  private HavingCondition parseHavingClause(IoTDBSqlParser.HavingClauseContext ctx) {
    Expression predicate = parseExpression(ctx.expression(), true);
    return new HavingCondition(predicate);
  }

  // ---- Order By Clause
  // all SortKeys should be contained by limitSet
  private OrderByComponent parseOrderByClause(
      IoTDBSqlParser.OrderByClauseContext ctx, ImmutableSet<String> limitSet) {
    OrderByComponent orderByComponent = new OrderByComponent();
    Set<String> sortKeySet = new HashSet<>();
    for (IoTDBSqlParser.OrderByAttributeClauseContext orderByAttributeClauseContext :
        ctx.orderByAttributeClause()) {
      // if the order by clause is unique, then the following sort keys will be ignored
      if (orderByComponent.isUnique()) {
        break;
      }
      SortItem sortItem = parseOrderByAttributeClause(orderByAttributeClauseContext, limitSet);

      String sortKey = sortItem.getSortKey();
      if (sortKeySet.contains(sortKey)) {
        continue;
      } else {
        sortKeySet.add(sortKey);
      }

      if (sortItem.isExpression()) {
        orderByComponent.addExpressionSortItem(sortItem);
      } else {
        orderByComponent.addSortItem(sortItem);
      }
    }
    return orderByComponent;
  }

  private SortItem parseOrderByAttributeClause(
      IoTDBSqlParser.OrderByAttributeClauseContext ctx, ImmutableSet<String> limitSet) {
    if (ctx.sortKey() != null) {
      String sortKey = ctx.sortKey().getText().toUpperCase();
      if (!limitSet.contains(sortKey)) {
        throw new SemanticException(
            String.format("ORDER BY: sort key[%s] is not contained in '%s'", sortKey, limitSet));
      }
      return new SortItem(sortKey, ctx.DESC() != null ? Ordering.DESC : Ordering.ASC);
    } else {
      Expression sortExpression = parseExpression(ctx.expression(), true);
      return new SortItem(
          sortExpression,
          ctx.DESC() != null ? Ordering.DESC : Ordering.ASC,
          ctx.FIRST() != null ? NullOrdering.FIRST : NullOrdering.LAST);
    }
  }

  // ---- Fill Clause
  public FillComponent parseFillClause(IoTDBSqlParser.FillClauseContext ctx) {
    FillComponent fillComponent = new FillComponent();
    if (ctx.LINEAR() != null) {
      fillComponent.setFillPolicy(FillPolicy.LINEAR);
    } else if (ctx.PREVIOUS() != null) {
      fillComponent.setFillPolicy(FillPolicy.PREVIOUS);

    } else if (ctx.constant() != null) {
      fillComponent.setFillPolicy(FillPolicy.VALUE);
      Literal fillValue = parseLiteral(ctx.constant());
      fillComponent.setFillValue(fillValue);
    } else {
      throw new SemanticException("Unknown FILL type.");
    }
    if (ctx.interval != null) {
      if (fillComponent.getFillPolicy() != FillPolicy.PREVIOUS) {
        throw new SemanticException(
            "Only FILL(PREVIOUS) support specifying the time duration threshold.");
      }
      fillComponent.setTimeDurationThreshold(
          DateTimeUtils.constructTimeDuration(ctx.interval.getText()));
    }
    return fillComponent;
  }

  private Literal parseLiteral(ConstantContext constantContext) {
    String text = constantContext.getText();
    if (constantContext.boolean_literal() != null) {
      return new BooleanLiteral(text);
    } else if (constantContext.STRING_LITERAL() != null) {
      return new StringLiteral(parseStringLiteral(text));
    } else if (constantContext.INTEGER_LITERAL() != null) {
      return new LongLiteral(text);
    } else if (constantContext.realLiteral() != null) {
      return new DoubleLiteral(text);
    } else if (constantContext.dateExpression() != null) {
      return new LongLiteral(parseDateExpression(constantContext.dateExpression()));
    } else {
      throw new SemanticException("Unsupported constant value in FILL: " + text);
    }
  }

  // parse LIMIT & OFFSET
  private long parseLimitClause(IoTDBSqlParser.LimitClauseContext ctx) {
    long limit;
    try {
      limit = Long.parseLong(ctx.INTEGER_LITERAL().getText());
    } catch (NumberFormatException e) {
      throw new SemanticException("Out of range. LIMIT <N>: N should be Int64.");
    }
    if (limit <= 0) {
      throw new SemanticException("LIMIT <N>: N should be greater than 0.");
    }
    return limit;
  }

  private long parseOffsetClause(IoTDBSqlParser.OffsetClauseContext ctx) {
    long offset;
    try {
      offset = Long.parseLong(ctx.INTEGER_LITERAL().getText());
    } catch (NumberFormatException e) {
      throw new SemanticException(
          "Out of range. OFFSET <OFFSETValue>: OFFSETValue should be Int64.");
    }
    if (offset < 0) {
      throw new SemanticException("OFFSET <OFFSETValue>: OFFSETValue should >= 0.");
    }
    return offset;
  }

  // parse SLIMIT & SOFFSET
  private int parseSLimitClause(IoTDBSqlParser.SlimitClauseContext ctx) {
    int slimit;
    try {
      slimit = Integer.parseInt(ctx.INTEGER_LITERAL().getText());
    } catch (NumberFormatException e) {
      throw new SemanticException("Out of range. SLIMIT <SN>: SN should be Int32.");
    }
    if (slimit <= 0) {
      throw new SemanticException("SLIMIT <SN>: SN should be greater than 0.");
    }
    return slimit;
  }

  // parse SOFFSET
  public int parseSOffsetClause(IoTDBSqlParser.SoffsetClauseContext ctx) {
    int soffset;
    try {
      soffset = Integer.parseInt(ctx.INTEGER_LITERAL().getText());
    } catch (NumberFormatException e) {
      throw new SemanticException(
          "Out of range. SOFFSET <SOFFSETValue>: SOFFSETValue should be Int32.");
    }
    if (soffset < 0) {
      throw new SemanticException("SOFFSET <SOFFSETValue>: SOFFSETValue should >= 0.");
    }
    return soffset;
  }

  // ---- Align By Clause
  private ResultSetFormat parseAlignBy(IoTDBSqlParser.AlignByClauseContext ctx) {
    if (ctx.DEVICE() != null) {
      return ResultSetFormat.ALIGN_BY_DEVICE;
    } else {
      return ResultSetFormat.ALIGN_BY_TIME;
    }
  }

  // Insert Statement ========================================================================

  @Override
  public Statement visitInsertStatement(IoTDBSqlParser.InsertStatementContext ctx) {
    InsertStatement insertStatement = new InsertStatement();
    insertStatement.setDevice(parsePrefixPath(ctx.prefixPath()));
    int timeIndex = parseInsertColumnSpec(ctx.insertColumnsSpec(), insertStatement);
    parseInsertValuesSpec(ctx.insertValuesSpec(), insertStatement, timeIndex);
    insertStatement.setAligned(ctx.ALIGNED() != null);
    return insertStatement;
  }

  private int parseInsertColumnSpec(
      IoTDBSqlParser.InsertColumnsSpecContext ctx, InsertStatement insertStatement) {
    List<String> measurementList = new ArrayList<>();
    int timeIndex = -1;

    for (int i = 0, size = ctx.insertColumn().size(); i < size; i++) {
      String measurement = parseInsertColumn(ctx.insertColumn(i));
      if ("time".equalsIgnoreCase(measurement) || "timestamp".equalsIgnoreCase(measurement)) {
        if (timeIndex != -1) {
          throw new SemanticException("One row should only have one time value");
        } else {
          timeIndex = i;
        }
      } else {
        measurementList.add(measurement);
      }
    }
    if (measurementList.isEmpty()) {
      throw new SemanticException("InsertStatement should contain at least one measurement");
    }
    insertStatement.setMeasurementList(measurementList.toArray(new String[0]));
    return timeIndex;
  }

  private String parseInsertColumn(IoTDBSqlParser.InsertColumnContext columnContext) {
    return parseNodeString(columnContext.getText());
  }

  private void parseInsertValuesSpec(
      IoTDBSqlParser.InsertValuesSpecContext ctx, InsertStatement insertStatement, int timeIndex) {
    List<IoTDBSqlParser.RowContext> rows = ctx.row();
    if (timeIndex == -1 && rows.size() != 1) {
      throw new SemanticException("need timestamps when insert multi rows");
    }
    List<String[]> valuesList = new ArrayList<>();
    long[] timeArray = new long[rows.size()];
    for (int i = 0, size = rows.size(); i < size; i++) {
      IoTDBSqlParser.RowContext row = rows.get(i);
      // parse timestamp
      long timestamp;
      List<String> valueList = new ArrayList<>();
      // using now() instead
      if (timeIndex == -1) {
        timestamp = CommonDateTimeUtils.currentTime();
      } else {
        timestamp = parseTimeValue(row.constant(timeIndex));
        TimestampPrecisionUtils.checkTimestampPrecision(timestamp);
      }
      timeArray[i] = timestamp;

      // parse values
      List<ConstantContext> values = row.constant();
      for (int j = 0, columnCount = values.size(); j < columnCount; j++) {
        if (j != timeIndex) {
          if (values.get(j).STRING_LITERAL() != null) {
            valueList.add(parseStringLiteralInInsertValue(values.get(j).getText()));
          } else {
            valueList.add(values.get(j).getText());
          }
        }
      }

      valuesList.add(valueList.toArray(new String[0]));
    }
    insertStatement.setTimes(timeArray);
    insertStatement.setValuesList(valuesList);
  }

  private long parseTimeValue(ConstantContext constant) {
    if (constant.INTEGER_LITERAL() != null) {
      try {
        if (constant.MINUS() != null) {
          return -Long.parseLong(constant.INTEGER_LITERAL().getText());
        }
        return Long.parseLong(constant.INTEGER_LITERAL().getText());
      } catch (NumberFormatException e) {
        throw new SemanticException(
            String.format("Can not parse %s to long value", constant.INTEGER_LITERAL().getText()));
      }
    } else if (constant.dateExpression() != null) {
      return parseDateExpression(constant.dateExpression(), CommonDateTimeUtils.currentTime());
    } else {
      throw new SemanticException(String.format("Can not parse %s to time", constant));
    }
  }

  // Load File

  @Override
  public Statement visitLoadFile(IoTDBSqlParser.LoadFileContext ctx) {
    try {
      LoadTsFileStatement loadTsFileStatement =
          new LoadTsFileStatement(parseStringLiteral(ctx.fileName.getText()));
      if (ctx.loadFileAttributeClauses() != null) {
        for (IoTDBSqlParser.LoadFileAttributeClauseContext attributeContext :
            ctx.loadFileAttributeClauses().loadFileAttributeClause()) {
          parseLoadFileAttributeClause(loadTsFileStatement, attributeContext);
        }
      }
      return loadTsFileStatement;
    } catch (FileNotFoundException e) {
      throw new SemanticException(e.getMessage());
    }
  }

  /**
   * Used for parsing load tsfile, context will be one of "SCHEMA, LEVEL, METADATA", and maybe
   * followed by a recursion property statement.
   *
   * @param loadTsFileStatement the result statement, setting by clause context
   * @param ctx context of property statement
   * @throws SemanticException if AUTOREGISTER | SGLEVEL | VERIFY are not specified
   */
  private void parseLoadFileAttributeClause(
      LoadTsFileStatement loadTsFileStatement, IoTDBSqlParser.LoadFileAttributeClauseContext ctx) {
    if (ctx.ONSUCCESS() != null) {
      loadTsFileStatement.setDeleteAfterLoad(ctx.DELETE() != null);
    } else if (ctx.SGLEVEL() != null) {
      loadTsFileStatement.setDatabaseLevel(Integer.parseInt(ctx.INTEGER_LITERAL().getText()));
    } else if (ctx.VERIFY() != null) {
      loadTsFileStatement.setVerifySchema(Boolean.parseBoolean(ctx.boolean_literal().getText()));
    } else {
      throw new SemanticException(
          String.format(
              "Load tsfile format %s error, please input AUTOREGISTER | SGLEVEL | VERIFY.",
              ctx.getText()));
    }
  }

  /** Common Parsers. */

  // IoTDB Objects ========================================================================
  private PartialPath parseFullPath(IoTDBSqlParser.FullPathContext ctx) {
    List<IoTDBSqlParser.NodeNameWithoutWildcardContext> nodeNamesWithoutStar =
        ctx.nodeNameWithoutWildcard();
    String[] path = new String[nodeNamesWithoutStar.size() + 1];
    int i = 0;
    if (ctx.ROOT() != null) {
      path[0] = ctx.ROOT().getText();
    }
    for (IoTDBSqlParser.NodeNameWithoutWildcardContext nodeNameWithoutStar : nodeNamesWithoutStar) {
      i++;
      path[i] = parseNodeNameWithoutWildCard(nodeNameWithoutStar);
    }
    return new PartialPath(path);
  }

  private PartialPath parseFullPathInExpression(
      IoTDBSqlParser.FullPathInExpressionContext ctx, boolean canUseFullPath) {
    List<IoTDBSqlParser.NodeNameContext> nodeNames = ctx.nodeName();
    int size = nodeNames.size();
    if (ctx.ROOT() != null) {
      if (!canUseFullPath) {
        // now full path cannot occur in SELECT only
        throw new SemanticException("Path can not start with root in select clause.");
      }
      size++;
    }
    String[] path = new String[size];
    if (ctx.ROOT() != null) {
      path[0] = ctx.ROOT().getText();
      for (int i = 0; i < nodeNames.size(); i++) {
        path[i + 1] = parseNodeName(nodeNames.get(i));
      }
    } else {
      for (int i = 0; i < nodeNames.size(); i++) {
        path[i] = parseNodeName(nodeNames.get(i));
      }
    }
    if (!lastLevelUseWildcard
        && !nodeNames.isEmpty()
        && !nodeNames.get(nodeNames.size() - 1).wildcard().isEmpty()) {
      lastLevelUseWildcard = true;
    }
    return new PartialPath(path);
  }

  private PartialPath parseFullPathInIntoPath(IoTDBSqlParser.FullPathInIntoPathContext ctx) {
    List<IoTDBSqlParser.NodeNameInIntoPathContext> nodeNames = ctx.nodeNameInIntoPath();
    String[] path = new String[nodeNames.size() + 1];
    int i = 0;
    if (ctx.ROOT() != null) {
      path[0] = ctx.ROOT().getText();
    }
    for (IoTDBSqlParser.NodeNameInIntoPathContext nodeName : nodeNames) {
      i++;
      path[i] = parseNodeNameInIntoPath(nodeName);
    }
    return new PartialPath(path);
  }

  private PartialPath parsePrefixPath(IoTDBSqlParser.PrefixPathContext ctx) {
    List<IoTDBSqlParser.NodeNameContext> nodeNames = ctx.nodeName();
    String[] path = new String[nodeNames.size() + 1];
    path[0] = ctx.ROOT().getText();
    for (int i = 0; i < nodeNames.size(); i++) {
      path[i + 1] = parseNodeName(nodeNames.get(i));
    }
    return new PartialPath(path);
  }

  private String parseNodeName(IoTDBSqlParser.NodeNameContext ctx) {
    if (!useWildcard && !ctx.wildcard().isEmpty()) {
      useWildcard = true;
    }
    return parseNodeString(ctx.getText());
  }

  private String parseNodeNameWithoutWildCard(IoTDBSqlParser.NodeNameWithoutWildcardContext ctx) {
    return parseNodeString(ctx.getText());
  }

  private String parseNodeNameInIntoPath(IoTDBSqlParser.NodeNameInIntoPathContext ctx) {
    return parseNodeStringInIntoPath(ctx.getText());
  }

  public static String parseNodeString(String nodeName) {
    if (nodeName.equals(IoTDBConstant.ONE_LEVEL_PATH_WILDCARD)
        || nodeName.equals(IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD)) {
      return nodeName;
    }
    if (nodeName.startsWith(TsFileConstant.BACK_QUOTE_STRING)
        && nodeName.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
      return PathUtils.removeBackQuotesIfNecessary(nodeName);
    }
    checkNodeName(nodeName);
    return nodeName;
  }

  private static String parseNodeStringInIntoPath(String nodeName) {
    if (nodeName.equals(IoTDBConstant.DOUBLE_COLONS)) {
      return nodeName;
    }
    if (nodeName.startsWith(TsFileConstant.BACK_QUOTE_STRING)
        && nodeName.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
      // needn't remove back_quotes here, we will remove them after placeholders applied
      return nodeName;
    }
    checkNodeNameInIntoPath(nodeName);
    return nodeName;
  }

  private static void checkNodeName(String src) {
    // node name could start with * and end with *
    if (!TsFileConstant.NODE_NAME_PATTERN.matcher(src).matches()) {
      throw new SemanticException(
          String.format(
              "%s is illegal, unquoted node name can only consist of digits, characters and underscore, or start or end with wildcard",
              src));
    }
  }

  private static void checkNodeNameInIntoPath(String src) {
    // ${} are allowed
    if (!NODE_NAME_IN_INTO_PATH_PATTERN.matcher(src).matches()) {
      throw new SemanticException(
          String.format(
              "%s is illegal, unquoted node name in select into clause can only consist of digits, characters, $, { and }",
              src));
    }
  }

  private static void checkIdentifier(String src) {
    if (!TsFileConstant.IDENTIFIER_PATTERN.matcher(src).matches() || PathUtils.isRealNumber(src)) {
      throw new SemanticException(
          String.format(
              "%s is illegal, identifier not enclosed with backticks can only consist of digits, characters and underscore.",
              src));
    }
  }

  // Literals ========================================================================

  public long parseDateFormat(String timestampStr) {
    if (timestampStr == null || "".equals(timestampStr.trim())) {
      throw new SemanticException("input timestamp cannot be empty");
    }
    if (timestampStr.equalsIgnoreCase(SqlConstant.NOW_FUNC)) {
      return CommonDateTimeUtils.currentTime();
    }
    try {
      return DateTimeUtils.convertDatetimeStrToLong(timestampStr, zoneId);
    } catch (Exception e) {
      throw new SemanticException(
          String.format(
              "Input time format %s error. "
                  + "Input like yyyy-MM-dd HH:mm:ss, yyyy-MM-ddTHH:mm:ss or "
                  + "refer to user document for more info.",
              timestampStr));
    }
  }

  public long parseDateFormat(String timestampStr, long currentTime) {
    if (timestampStr == null || "".equals(timestampStr.trim())) {
      throw new SemanticException("input timestamp cannot be empty");
    }
    if (timestampStr.equalsIgnoreCase(SqlConstant.NOW_FUNC)) {
      return currentTime;
    }
    try {
      return DateTimeUtils.convertDatetimeStrToLong(timestampStr, zoneId);
    } catch (Exception e) {
      throw new SemanticException(
          String.format(
              "Input time format %s error. "
                  + "Input like yyyy-MM-dd HH:mm:ss, yyyy-MM-ddTHH:mm:ss or "
                  + "refer to user document for more info.",
              timestampStr));
    }
  }

  private String parseStringLiteral(String src) {
    if (2 <= src.length()) {
      // do not unescape string
      String unWrappedString = src.substring(1, src.length() - 1);
      if (src.charAt(0) == '\"' && src.charAt(src.length() - 1) == '\"') {
        // replace "" with "
        String replaced = unWrappedString.replace("\"\"", "\"");
        return replaced.length() == 0 ? "" : replaced;
      }
      if ((src.charAt(0) == '\'' && src.charAt(src.length() - 1) == '\'')) {
        // replace '' with '
        String replaced = unWrappedString.replace("''", "'");
        return replaced.length() == 0 ? "" : replaced;
      }
    }
    return src;
  }

  private String parseStringLiteralInInsertValue(String src) {
    if (2 <= src.length()
        && ((src.charAt(0) == '\"' && src.charAt(src.length() - 1) == '\"')
            || (src.charAt(0) == '\'' && src.charAt(src.length() - 1) == '\''))) {
      return "'" + parseStringLiteral(src) + "'";
    }
    return src;
  }

  public static String parseIdentifier(String src) {
    if (src.startsWith(TsFileConstant.BACK_QUOTE_STRING)
        && src.endsWith(TsFileConstant.BACK_QUOTE_STRING)) {
      return src.substring(1, src.length() - 1)
          .replace(TsFileConstant.DOUBLE_BACK_QUOTE_STRING, TsFileConstant.BACK_QUOTE_STRING);
    }
    checkIdentifier(src);
    return src;
  }

  // Alias

  /** Function for parsing Alias of ResultColumn. */
  private String parseAlias(IoTDBSqlParser.AliasContext ctx) {
    String alias;
    if (ctx.constant() != null) {
      alias = parseConstant(ctx.constant());
    } else {
      alias = parseIdentifier(ctx.identifier().getText());
    }
    return alias;
  }

  /**
   * Function for parsing AliasNode.
   *
   * @throws SemanticException if the alias pattern is not supported
   */
  private String parseAliasNode(IoTDBSqlParser.AliasContext ctx) {
    String alias;
    if (ctx.constant() != null) {
      alias = parseConstant(ctx.constant());
      if (PathUtils.isRealNumber(alias)
          || !TsFileConstant.IDENTIFIER_PATTERN.matcher(alias).matches()) {
        throw new SemanticException("Not support for this alias, Please enclose in back quotes.");
      }
    } else {
      alias = parseNodeString(ctx.identifier().getText());
    }
    return alias;
  }

  /** Data Control Language (DCL). */

  // Create User
  @Override
  public Statement visitCreateUser(IoTDBSqlParser.CreateUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.CREATE_USER);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    authorStatement.setPassWord(parseStringLiteral(ctx.password.getText()));
    return authorStatement;
  }

  // Create Role

  @Override
  public Statement visitCreateRole(IoTDBSqlParser.CreateRoleContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.CREATE_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    return authorStatement;
  }

  // Alter Password
  @Override
  public Statement visitAlterUser(IoTDBSqlParser.AlterUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.UPDATE_USER);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    authorStatement.setNewPassword(parseStringLiteral(ctx.password.getText()));
    return authorStatement;
  }

  // Grant User Privileges
  @Override
  public Statement visitGrantUser(IoTDBSqlParser.GrantUserContext ctx) {
    String[] privileges = parsePrivilege(ctx.privileges());
    List<PartialPath> nodeNameList =
        ctx.prefixPath().stream()
            .map(this::parsePrefixPath)
            .distinct()
            .collect(Collectors.toList());
    checkGrantRevokePrivileges(privileges, nodeNameList);
    String[] priviParsed = parsePrivilege(privileges);

    AuthorStatement authorStatement = new AuthorStatement(AuthorType.GRANT_USER);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    authorStatement.setPrivilegeList(priviParsed);
    authorStatement.setNodeNameList(nodeNameList);
    authorStatement.setGrantOpt(ctx.grantOpt() != null);
    return authorStatement;
  }

  // Grant Role Privileges

  @Override
  public Statement visitGrantRole(IoTDBSqlParser.GrantRoleContext ctx) {
    String[] privileges = parsePrivilege(ctx.privileges());
    List<PartialPath> nodeNameList =
        ctx.prefixPath().stream()
            .map(this::parsePrefixPath)
            .distinct()
            .collect(Collectors.toList());
    checkGrantRevokePrivileges(privileges, nodeNameList);
    String[] priviParsed = parsePrivilege(privileges);

    AuthorStatement authorStatement = new AuthorStatement(AuthorType.GRANT_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    authorStatement.setPrivilegeList(priviParsed);
    authorStatement.setNodeNameList(nodeNameList);
    authorStatement.setGrantOpt(ctx.grantOpt() != null);
    return authorStatement;
  }

  // Grant User Role

  @Override
  public Statement visitGrantRoleToUser(IoTDBSqlParser.GrantRoleToUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.GRANT_USER_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    return authorStatement;
  }

  // Revoke User Privileges

  @Override
  public Statement visitRevokeUser(IoTDBSqlParser.RevokeUserContext ctx) {
    String[] privileges = parsePrivilege(ctx.privileges());
    List<PartialPath> nodeNameList =
        ctx.prefixPath().stream()
            .map(this::parsePrefixPath)
            .distinct()
            .collect(Collectors.toList());
    checkGrantRevokePrivileges(privileges, nodeNameList);
    String[] priviParsed = parsePrivilege(privileges);

    AuthorStatement authorStatement = new AuthorStatement(AuthorType.REVOKE_USER);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    authorStatement.setPrivilegeList(priviParsed);
    authorStatement.setNodeNameList(nodeNameList);
    return authorStatement;
  }

  // Revoke Role Privileges

  @Override
  public Statement visitRevokeRole(IoTDBSqlParser.RevokeRoleContext ctx) {
    String[] privileges = parsePrivilege(ctx.privileges());
    List<PartialPath> nodeNameList =
        ctx.prefixPath().stream()
            .map(this::parsePrefixPath)
            .distinct()
            .collect(Collectors.toList());
    checkGrantRevokePrivileges(privileges, nodeNameList);
    String[] priviParsed = parsePrivilege(privileges);

    AuthorStatement authorStatement = new AuthorStatement(AuthorType.REVOKE_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    authorStatement.setPrivilegeList(priviParsed);
    authorStatement.setNodeNameList(nodeNameList);
    return authorStatement;
  }

  private void checkGrantRevokePrivileges(String[] privileges, List<PartialPath> nodeNameList) {
    // 1. all grant or revoke statements need target path.
    if (nodeNameList.isEmpty()) {
      throw new SemanticException("Statement needs target paths");
    }

    // 2. if privilege list has system privilege or "ALL", nodeNameList must only contain "root.**".
    boolean hasSystemPri = false;
    String errorPrivilegeName = "";

    for (String privilege : privileges) {
      if ("ALL".equalsIgnoreCase(privilege)
          || (!"READ".equalsIgnoreCase(privilege)
              && !"WRITE".equalsIgnoreCase(privilege)
              && !PrivilegeType.valueOf(privilege.toUpperCase()).isPathRelevant())) {
        hasSystemPri = true;
        errorPrivilegeName = privilege.toUpperCase();
        break;
      }
    }
    if (hasSystemPri
        && !(nodeNameList.size() == 1
            && nodeNameList.contains(new PartialPath(ALL_RESULT_NODES)))) {
      throw new SemanticException(
          String.format("[%s] can only be set on path: root.**", errorPrivilegeName));
    }
  }

  private String[] parsePrivilege(String[] privileges) {
    Set<String> privSet = new HashSet<>();
    for (String priv : privileges) {
      if (priv.equalsIgnoreCase("READ")) {
        privSet.add("READ_SCHEMA");
        privSet.add("READ_DATA");
        continue;
      } else if (priv.equalsIgnoreCase("WRITE")) {
        privSet.add("WRITE_DATA");
        privSet.add("WRITE_SCHEMA");
        continue;
      } else if (priv.equalsIgnoreCase("ALL")) {
        for (PrivilegeType type : PrivilegeType.values()) {
          privSet.add(type.toString());
        }
        continue;
      }
      privSet.add(priv);
    }
    return privSet.toArray(new String[0]);
  }

  // Revoke Role From User
  @Override
  public Statement visitRevokeRoleFromUser(IoTDBSqlParser.RevokeRoleFromUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.REVOKE_USER_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    return authorStatement;
  }

  // Drop User

  @Override
  public Statement visitDropUser(IoTDBSqlParser.DropUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.DROP_USER);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    return authorStatement;
  }

  // Drop Role

  @Override
  public Statement visitDropRole(IoTDBSqlParser.DropRoleContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.DROP_ROLE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    return authorStatement;
  }

  // List Users

  @Override
  public Statement visitListUser(IoTDBSqlParser.ListUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.LIST_USER);
    if (ctx.roleName != null) {
      authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    }
    return authorStatement;
  }

  // List Roles

  @Override
  public Statement visitListRole(IoTDBSqlParser.ListRoleContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.LIST_ROLE);
    if (ctx.userName != null) {
      authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    }
    return authorStatement;
  }

  // List Privileges

  @Override
  public Statement visitListPrivilegesUser(IoTDBSqlParser.ListPrivilegesUserContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.LIST_USER_PRIVILEGE);
    authorStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    return authorStatement;
  }

  // List Privileges of Roles On Specific Path

  @Override
  public Statement visitListPrivilegesRole(IoTDBSqlParser.ListPrivilegesRoleContext ctx) {
    AuthorStatement authorStatement = new AuthorStatement(AuthorType.LIST_ROLE_PRIVILEGE);
    authorStatement.setRoleName(parseIdentifier(ctx.roleName.getText()));
    return authorStatement;
  }

  private String[] parsePrivilege(IoTDBSqlParser.PrivilegesContext ctx) {
    List<IoTDBSqlParser.PrivilegeValueContext> privilegeList = ctx.privilegeValue();
    List<String> privileges = new ArrayList<>();
    for (IoTDBSqlParser.PrivilegeValueContext privilegeValue : privilegeList) {
      privileges.add(privilegeValue.getText());
    }
    return privileges.toArray(new String[0]);
  }

  // Create database
  @Override
  public Statement visitCreateDatabase(IoTDBSqlParser.CreateDatabaseContext ctx) {
    DatabaseSchemaStatement databaseSchemaStatement =
        new DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.CREATE);
    PartialPath path = parsePrefixPath(ctx.prefixPath());
    databaseSchemaStatement.setDatabasePath(path);
    if (ctx.databaseAttributesClause() != null) {
      parseDatabaseAttributesClause(databaseSchemaStatement, ctx.databaseAttributesClause());
    }
    return databaseSchemaStatement;
  }

  @Override
  public Statement visitAlterDatabase(IoTDBSqlParser.AlterDatabaseContext ctx) {
    DatabaseSchemaStatement databaseSchemaStatement =
        new DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.ALTER);
    PartialPath path = parsePrefixPath(ctx.prefixPath());
    databaseSchemaStatement.setDatabasePath(path);
    parseDatabaseAttributesClause(databaseSchemaStatement, ctx.databaseAttributesClause());
    return databaseSchemaStatement;
  }

  private void parseDatabaseAttributesClause(
      DatabaseSchemaStatement databaseSchemaStatement,
      IoTDBSqlParser.DatabaseAttributesClauseContext ctx) {
    for (IoTDBSqlParser.DatabaseAttributeClauseContext attribute : ctx.databaseAttributeClause()) {
      IoTDBSqlParser.DatabaseAttributeKeyContext attributeKey = attribute.databaseAttributeKey();
      if (attributeKey.TTL() != null) {
        long ttl = Long.parseLong(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setTtl(ttl);
      } else if (attributeKey.SCHEMA_REPLICATION_FACTOR() != null) {
        int schemaReplicationFactor = Integer.parseInt(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setSchemaReplicationFactor(schemaReplicationFactor);
      } else if (attributeKey.DATA_REPLICATION_FACTOR() != null) {
        int dataReplicationFactor = Integer.parseInt(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setDataReplicationFactor(dataReplicationFactor);
      } else if (attributeKey.TIME_PARTITION_INTERVAL() != null) {
        long timePartitionInterval = Long.parseLong(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setTimePartitionInterval(timePartitionInterval);
      } else if (attributeKey.SCHEMA_REGION_GROUP_NUM() != null) {
        int schemaRegionGroupNum = Integer.parseInt(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setSchemaRegionGroupNum(schemaRegionGroupNum);
      } else if (attributeKey.DATA_REGION_GROUP_NUM() != null) {
        int dataRegionGroupNum = Integer.parseInt(attribute.INTEGER_LITERAL().getText());
        databaseSchemaStatement.setDataRegionGroupNum(dataRegionGroupNum);
      }
    }
  }

  @Override
  public Statement visitSetTTL(IoTDBSqlParser.SetTTLContext ctx) {
    SetTTLStatement setTTLStatement = new SetTTLStatement();
    PartialPath path = parsePrefixPath(ctx.prefixPath());
    long ttl = Long.parseLong(ctx.INTEGER_LITERAL().getText());
    setTTLStatement.setDatabasePath(path);
    setTTLStatement.setTTL(ttl);
    return setTTLStatement;
  }

  @Override
  public Statement visitUnsetTTL(IoTDBSqlParser.UnsetTTLContext ctx) {
    UnSetTTLStatement unSetTTLStatement = new UnSetTTLStatement();
    PartialPath partialPath = parsePrefixPath(ctx.prefixPath());
    unSetTTLStatement.setDatabasePath(partialPath);
    return unSetTTLStatement;
  }

  @Override
  public Statement visitShowTTL(IoTDBSqlParser.ShowTTLContext ctx) {
    ShowTTLStatement showTTLStatement = new ShowTTLStatement();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
      PartialPath partialPath = parsePrefixPath(prefixPathContext);
      showTTLStatement.addPathPatterns(partialPath);
    }
    return showTTLStatement;
  }

  @Override
  public Statement visitShowAllTTL(IoTDBSqlParser.ShowAllTTLContext ctx) {
    ShowTTLStatement showTTLStatement = new ShowTTLStatement();
    showTTLStatement.setAll(true);
    return showTTLStatement;
  }

  @Override
  public Statement visitShowVariables(IoTDBSqlParser.ShowVariablesContext ctx) {
    return new ShowVariablesStatement();
  }

  @Override
  public Statement visitShowCluster(IoTDBSqlParser.ShowClusterContext ctx) {
    ShowClusterStatement showClusterStatement = new ShowClusterStatement();
    if (ctx.DETAILS() != null) {
      showClusterStatement.setDetails(true);
    }
    return showClusterStatement;
  }

  @Override
  public Statement visitShowClusterId(IoTDBSqlParser.ShowClusterIdContext ctx) {
    return new ShowClusterIdStatement();
  }

  @Override
  public Statement visitDropDatabase(IoTDBSqlParser.DropDatabaseContext ctx) {
    DeleteDatabaseStatement dropDatabaseStatement = new DeleteDatabaseStatement();
    List<IoTDBSqlParser.PrefixPathContext> prefixPathContexts = ctx.prefixPath();
    List<String> paths = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : prefixPathContexts) {
      paths.add(parsePrefixPath(prefixPathContext).getFullPath());
    }
    dropDatabaseStatement.setPrefixPath(paths);
    return dropDatabaseStatement;
  }

  // Explain ========================================================================
  @Override
  public Statement visitExplain(IoTDBSqlParser.ExplainContext ctx) {
    QueryStatement queryStatement = (QueryStatement) visitSelectStatement(ctx.selectStatement());
    return new ExplainStatement(queryStatement);
  }

  @Override
  public Statement visitDeleteStatement(IoTDBSqlParser.DeleteStatementContext ctx) {
    DeleteDataStatement statement = new DeleteDataStatement();
    List<IoTDBSqlParser.PrefixPathContext> prefixPaths = ctx.prefixPath();
    List<PartialPath> pathList = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPath : prefixPaths) {
      pathList.add(parsePrefixPath(prefixPath));
    }
    statement.setPathList(pathList);
    if (ctx.whereClause() != null) {
      WhereCondition whereCondition = parseWhereClause(ctx.whereClause());
      TimeRange timeRange = parseDeleteTimeRange(whereCondition.getPredicate());
      statement.setTimeRange(timeRange);
    } else {
      statement.setTimeRange(new TimeRange(Long.MIN_VALUE, Long.MAX_VALUE));
    }
    return statement;
  }

  private TimeRange parseDeleteTimeRange(Expression predicate) {
    if (predicate instanceof LogicAndExpression) {
      TimeRange leftTimeRange =
          parseDeleteTimeRange(((LogicAndExpression) predicate).getLeftExpression());
      TimeRange rightTimeRange =
          parseDeleteTimeRange(((LogicAndExpression) predicate).getRightExpression());
      return new TimeRange(
          Math.max(leftTimeRange.getMin(), rightTimeRange.getMin()),
          Math.min(leftTimeRange.getMax(), rightTimeRange.getMax()));
    } else if (predicate instanceof CompareBinaryExpression) {
      if (((CompareBinaryExpression) predicate).getLeftExpression() instanceof TimestampOperand) {
        return parseTimeRangeForDeleteTimeRange(
            predicate.getExpressionType(),
            ((CompareBinaryExpression) predicate).getLeftExpression(),
            ((CompareBinaryExpression) predicate).getRightExpression());
      } else {
        return parseTimeRangeForDeleteTimeRange(
            predicate.getExpressionType(),
            ((CompareBinaryExpression) predicate).getRightExpression(),
            ((CompareBinaryExpression) predicate).getLeftExpression());
      }
    } else {
      throw new SemanticException(DELETE_RANGE_ERROR_MSG);
    }
  }

  private TimeRange parseTimeRangeForDeleteTimeRange(
      ExpressionType expressionType, Expression timeExpression, Expression valueExpression) {
    if (!(timeExpression instanceof TimestampOperand)
        || !(valueExpression instanceof ConstantOperand)) {
      throw new SemanticException(DELETE_ONLY_SUPPORT_TIME_EXP_ERROR_MSG);
    }

    if (((ConstantOperand) valueExpression).getDataType() != TSDataType.INT64) {
      throw new SemanticException("The datatype of timestamp should be LONG.");
    }

    long time = Long.parseLong(((ConstantOperand) valueExpression).getValueString());
    switch (expressionType) {
      case LESS_THAN:
        return new TimeRange(Long.MIN_VALUE, time - 1);
      case LESS_EQUAL:
        return new TimeRange(Long.MIN_VALUE, time);
      case GREATER_THAN:
        return new TimeRange(time + 1, Long.MAX_VALUE);
      case GREATER_EQUAL:
        return new TimeRange(time, Long.MAX_VALUE);
      case EQUAL_TO:
        return new TimeRange(time, time);
      default:
        throw new SemanticException(DELETE_RANGE_ERROR_MSG);
    }
  }

  /** function for parsing file path used by LOAD statement. */
  public String parseFilePath(String src) {
    return src.substring(1, src.length() - 1);
  }

  // Expression & Predicate ========================================================================

  private Expression parseExpression(
      IoTDBSqlParser.ExpressionContext context, boolean canUseFullPath) {
    if (context.unaryInBracket != null) {
      return parseExpression(context.unaryInBracket, canUseFullPath);
    }

    if (context.expressionAfterUnaryOperator != null) {
      if (context.MINUS() != null) {
        return new NegationExpression(
            parseExpression(context.expressionAfterUnaryOperator, canUseFullPath));
      }
      if (context.operator_not() != null) {
        return new LogicNotExpression(
            parseExpression(context.expressionAfterUnaryOperator, canUseFullPath));
      }
      return parseExpression(context.expressionAfterUnaryOperator, canUseFullPath);
    }

    if (context.leftExpression != null && context.rightExpression != null) {
      Expression leftExpression = parseExpression(context.leftExpression, canUseFullPath);
      Expression rightExpression = parseExpression(context.rightExpression, canUseFullPath);
      if (context.STAR() != null) {
        return new MultiplicationExpression(leftExpression, rightExpression);
      }
      if (context.DIV() != null) {
        return new DivisionExpression(leftExpression, rightExpression);
      }
      if (context.MOD() != null) {
        return new ModuloExpression(leftExpression, rightExpression);
      }
      if (context.PLUS() != null) {
        return new AdditionExpression(leftExpression, rightExpression);
      }
      if (context.MINUS() != null) {
        return new SubtractionExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_GT() != null) {
        return new GreaterThanExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_GTE() != null) {
        return new GreaterEqualExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_LT() != null) {
        return new LessThanExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_LTE() != null) {
        return new LessEqualExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_DEQ() != null || context.OPERATOR_SEQ() != null) {
        return new EqualToExpression(leftExpression, rightExpression);
      }
      if (context.OPERATOR_NEQ() != null) {
        return new NonEqualExpression(leftExpression, rightExpression);
      }
      if (context.operator_and() != null) {
        return new LogicAndExpression(leftExpression, rightExpression);
      }
      if (context.operator_or() != null) {
        return new LogicOrExpression(leftExpression, rightExpression);
      }
      throw new UnsupportedOperationException();
    }

    if (context.unaryBeforeRegularOrLikeExpression != null) {
      if (context.REGEXP() != null) {
        return parseRegularExpression(context, canUseFullPath);
      }
      if (context.LIKE() != null) {
        return parseLikeExpression(context, canUseFullPath);
      }
      throw new UnsupportedOperationException();
    }

    if (context.unaryBeforeIsNullExpression != null) {
      return parseIsNullExpression(context, canUseFullPath);
    }

    if (context.firstExpression != null
        && context.secondExpression != null
        && context.thirdExpression != null) {
      Expression firstExpression = parseExpression(context.firstExpression, canUseFullPath);
      Expression secondExpression = parseExpression(context.secondExpression, canUseFullPath);
      Expression thirdExpression = parseExpression(context.thirdExpression, canUseFullPath);

      if (context.operator_between() != null) {
        return new BetweenExpression(
            firstExpression, secondExpression, thirdExpression, context.operator_not() != null);
      }
      throw new UnsupportedOperationException();
    }

    if (context.unaryBeforeInExpression != null) {
      return parseInExpression(context, canUseFullPath);
    }

    if (context.scalarFunctionExpression() != null) {
      return parseScalarFunctionExpression(context.scalarFunctionExpression(), canUseFullPath);
    }

    if (context.functionName() != null) {
      return parseFunctionExpression(context, canUseFullPath);
    }

    if (context.fullPathInExpression() != null) {
      return new TimeSeriesOperand(
          parseFullPathInExpression(context.fullPathInExpression(), canUseFullPath));
    }

    if (context.time != null) {
      return new TimestampOperand();
    }

    if (context.constant() != null && !context.constant().isEmpty()) {
      return parseConstantOperand(context.constant(0));
    }

    if (context.caseWhenThenExpression() != null) {
      return parseCaseWhenThenExpression(context.caseWhenThenExpression(), canUseFullPath);
    }

    throw new UnsupportedOperationException();
  }

  private Expression parseScalarFunctionExpression(
      IoTDBSqlParser.ScalarFunctionExpressionContext context, boolean canUseFullPath) {
    if (context.CAST() != null) {
      return parseCastFunction(context, canUseFullPath);
    } else if (context.REPLACE() != null) {
      return parseReplaceFunction(context, canUseFullPath);
    } else if (context.ROUND() != null) {
      return parseRoundFunction(context, canUseFullPath);
    } else if (context.SUBSTRING() != null) {
      return parseSubStrFunction(context, canUseFullPath);
    }
    throw new UnsupportedOperationException();
  }

  private Expression parseCastFunction(
      IoTDBSqlParser.ScalarFunctionExpressionContext castClause, boolean canUseFullPath) {
    FunctionExpression functionExpression = new FunctionExpression(CAST_FUNCTION);
    functionExpression.addExpression(parseExpression(castClause.castInput, canUseFullPath));
    functionExpression.addAttribute(CAST_TYPE, parseAttributeValue(castClause.attributeValue()));
    return functionExpression;
  }

  private Expression parseReplaceFunction(
      IoTDBSqlParser.ScalarFunctionExpressionContext replaceClause, boolean canUseFullPath) {
    FunctionExpression functionExpression = new FunctionExpression(REPLACE_FUNCTION);
    functionExpression.addExpression(parseExpression(replaceClause.text, canUseFullPath));
    functionExpression.addAttribute(REPLACE_FROM, parseStringLiteral(replaceClause.from.getText()));
    functionExpression.addAttribute(REPLACE_TO, parseStringLiteral(replaceClause.to.getText()));
    return functionExpression;
  }

  private Expression parseSubStrFunction(
      IoTDBSqlParser.ScalarFunctionExpressionContext subStrClause, boolean canUseFullPath) {
    FunctionExpression functionExpression = new FunctionExpression(SUBSTRING_FUNCTION);
    IoTDBSqlParser.SubStringExpressionContext subStringExpression =
        subStrClause.subStringExpression();
    functionExpression.addExpression(parseExpression(subStringExpression.input, canUseFullPath));
    if (subStringExpression.startPosition != null) {
      functionExpression.addAttribute(SUBSTRING_START, subStringExpression.startPosition.getText());
      if (subStringExpression.length != null) {
        functionExpression.addAttribute(SUBSTRING_LENGTH, subStringExpression.length.getText());
      }
    }
    if (subStringExpression.from != null) {
      functionExpression.addAttribute(SUBSTRING_IS_STANDARD, "0");
      functionExpression.addAttribute(
          SUBSTRING_START, parseStringLiteral(subStringExpression.from.getText()));
      if (subStringExpression.forLength != null) {
        functionExpression.addAttribute(SUBSTRING_LENGTH, subStringExpression.forLength.getText());
      }
    }
    return functionExpression;
  }

  private Expression parseRoundFunction(
      IoTDBSqlParser.ScalarFunctionExpressionContext roundClause, boolean canUseFullPath) {
    FunctionExpression functionExpression = new FunctionExpression(ROUND_FUNCTION);
    functionExpression.addExpression(parseExpression(roundClause.input, canUseFullPath));
    if (roundClause.places != null) {
      functionExpression.addAttribute(ROUND_PLACES, parseConstant(roundClause.constant()));
    }
    return functionExpression;
  }

  private CaseWhenThenExpression parseCaseWhenThenExpression(
      IoTDBSqlParser.CaseWhenThenExpressionContext context, boolean canUseFullPath) {
    // handle CASE
    Expression caseExpression = null;
    boolean simpleCase = false;
    if (context.caseExpression != null) {
      caseExpression = parseExpression(context.caseExpression, canUseFullPath);
      simpleCase = true;
    }
    // handle WHEN-THEN
    List<WhenThenExpression> whenThenList = new ArrayList<>();
    if (simpleCase) {
      for (IoTDBSqlParser.WhenThenExpressionContext whenThenExpressionContext :
          context.whenThenExpression()) {
        Expression when = parseExpression(whenThenExpressionContext.whenExpression, canUseFullPath);
        Expression then = parseExpression(whenThenExpressionContext.thenExpression, canUseFullPath);
        Expression comparison = new EqualToExpression(caseExpression, when);
        whenThenList.add(new WhenThenExpression(comparison, then));
      }
    } else {
      for (IoTDBSqlParser.WhenThenExpressionContext whenThenExpressionContext :
          context.whenThenExpression()) {
        whenThenList.add(
            new WhenThenExpression(
                parseExpression(whenThenExpressionContext.whenExpression, canUseFullPath),
                parseExpression(whenThenExpressionContext.thenExpression, canUseFullPath)));
      }
    }
    // handle ELSE
    Expression elseExpression = new NullOperand();
    if (context.elseExpression != null) {
      elseExpression = parseExpression(context.elseExpression, canUseFullPath);
    }
    return new CaseWhenThenExpression(whenThenList, elseExpression);
  }

  private Expression parseFunctionExpression(
      IoTDBSqlParser.ExpressionContext functionClause, boolean canUseFullPath) {
    FunctionExpression functionExpression =
        new FunctionExpression(parseIdentifier(functionClause.functionName().getText()));

    // expressions
    boolean hasNonPureConstantSubExpression = false;
    for (IoTDBSqlParser.ExpressionContext expression : functionClause.expression()) {
      Expression subexpression = parseExpression(expression, canUseFullPath);
      if (!subexpression.isConstantOperand()) {
        hasNonPureConstantSubExpression = true;
      }
      if (subexpression instanceof EqualToExpression) {
        Expression subLeftExpression = ((EqualToExpression) subexpression).getLeftExpression();
        Expression subRightExpression = ((EqualToExpression) subexpression).getRightExpression();
        if (subLeftExpression.isConstantOperand()
            && (!(subRightExpression.isConstantOperand()
                && ((ConstantOperand) subRightExpression).getDataType().equals(TSDataType.TEXT)))) {
          throw new SemanticException("Attributes of functions should be quoted with '' or \"\"");
        }
        if (subLeftExpression.isConstantOperand() && subRightExpression.isConstantOperand()) {
          // parse attribute
          functionExpression.addAttribute(
              ((ConstantOperand) subLeftExpression).getValueString(),
              ((ConstantOperand) subRightExpression).getValueString());
        } else {
          functionExpression.addExpression(subexpression);
        }
      } else {
        functionExpression.addExpression(subexpression);
      }
    }

    // It is not allowed to have function expressions like F(1, 1.0). There should be at least one
    // non-pure-constant sub-expression, otherwise the timestamp of the row cannot be inferred.
    if (!hasNonPureConstantSubExpression) {
      throw new SemanticException(
          "Invalid function expression, all the arguments are constant operands: "
              + functionClause.getText());
    }

    // check size of input expressions
    // type check of input expressions is put in ExpressionTypeAnalyzer
    if (functionExpression.isBuiltInAggregationFunctionExpression()) {
      checkAggregationFunctionInput(functionExpression);
    } else if (functionExpression.isBuiltInScalarFunction()) {
      checkBuiltInScalarFunctionInput(functionExpression);
    }
    return functionExpression;
  }

  private void checkAggregationFunctionInput(FunctionExpression functionExpression) {
    final String functionName = functionExpression.getFunctionName().toLowerCase();
    switch (functionName) {
      case SqlConstant.MIN_TIME:
      case SqlConstant.MAX_TIME:
      case SqlConstant.COUNT:
      case SqlConstant.COUNT_TIME:
      case SqlConstant.MIN_VALUE:
      case SqlConstant.LAST_VALUE:
      case SqlConstant.FIRST_VALUE:
      case SqlConstant.MAX_VALUE:
      case SqlConstant.EXTREME:
      case SqlConstant.AVG:
      case SqlConstant.SUM:
      case SqlConstant.TIME_DURATION:
      case SqlConstant.MODE:
      case SqlConstant.STDDEV:
      case SqlConstant.STDDEV_POP:
      case SqlConstant.STDDEV_SAMP:
      case SqlConstant.VARIANCE:
      case SqlConstant.VAR_POP:
      case SqlConstant.VAR_SAMP:
        checkFunctionExpressionInputSize(
            functionExpression.getExpressionString(),
            functionExpression.getExpressions().size(),
            1);
        return;
      case SqlConstant.COUNT_IF:
        checkFunctionExpressionInputSize(
            functionExpression.getExpressionString(),
            functionExpression.getExpressions().size(),
            2);
        return;
      default:
        throw new IllegalArgumentException(
            "Invalid Aggregation function: " + functionExpression.getFunctionName());
    }
  }

  private void checkBuiltInScalarFunctionInput(FunctionExpression functionExpression) {
    BuiltInScalarFunctionHelperFactory.createHelper(functionExpression.getFunctionName())
        .checkBuiltInScalarFunctionInputSize(functionExpression);
  }

  public static void checkFunctionExpressionInputSize(
      String expressionString, int actual, int... expected) {
    for (int expect : expected) {
      if (expect == actual) {
        return;
      }
    }
    throw new SemanticException(
        String.format(
            "Error size of input expressions. expression: %s, actual size: %s, expected size: %s.",
            expressionString, actual, Arrays.toString(expected)));
  }

  private Expression parseRegularExpression(ExpressionContext context, boolean canUseFullPath) {
    return new RegularExpression(
        parseExpression(context.unaryBeforeRegularOrLikeExpression, canUseFullPath),
        parseStringLiteral(context.STRING_LITERAL().getText()),
        false);
  }

  private Expression parseLikeExpression(ExpressionContext context, boolean canUseFullPath) {
    return new LikeExpression(
        parseExpression(context.unaryBeforeRegularOrLikeExpression, canUseFullPath),
        parseStringLiteral(context.STRING_LITERAL().getText()),
        false);
  }

  private Expression parseIsNullExpression(ExpressionContext context, boolean canUseFullPath) {
    return new IsNullExpression(
        parseExpression(context.unaryBeforeIsNullExpression, canUseFullPath),
        context.operator_not() != null);
  }

  private Expression parseInExpression(ExpressionContext context, boolean canUseFullPath) {
    Expression childExpression = parseExpression(context.unaryBeforeInExpression, canUseFullPath);
    LinkedHashSet<String> values = new LinkedHashSet<>();
    for (ConstantContext constantContext : context.constant()) {
      values.add(parseConstant(constantContext));
    }
    return new InExpression(childExpression, context.operator_not() != null, values);
  }

  private String parseConstant(ConstantContext constantContext) {
    String text = constantContext.getText();
    if (constantContext.boolean_literal() != null
        || constantContext.INTEGER_LITERAL() != null
        || constantContext.realLiteral() != null) {
      return text;
    } else if (constantContext.STRING_LITERAL() != null) {
      return parseStringLiteral(text);
    } else if (constantContext.dateExpression() != null) {
      return String.valueOf(parseDateExpression(constantContext.dateExpression()));
    } else {
      throw new IllegalArgumentException("Unsupported constant value: " + text);
    }
  }

  private Expression parseConstantOperand(ConstantContext constantContext) {
    String text = constantContext.getText();
    if (constantContext.boolean_literal() != null) {
      return new ConstantOperand(TSDataType.BOOLEAN, text);
    } else if (constantContext.STRING_LITERAL() != null) {
      return new ConstantOperand(TSDataType.TEXT, parseStringLiteral(text));
    } else if (constantContext.INTEGER_LITERAL() != null) {
      return new ConstantOperand(TSDataType.INT64, text);
    } else if (constantContext.realLiteral() != null) {
      return parseRealLiteral(text);
    } else if (constantContext.dateExpression() != null) {
      return new ConstantOperand(
          TSDataType.INT64, String.valueOf(parseDateExpression(constantContext.dateExpression())));
    } else {
      throw new SemanticException("Unsupported constant operand: " + text);
    }
  }

  private Expression parseRealLiteral(String value) {
    // 3.33 is float by default
    return new ConstantOperand(
        CONFIG.getFloatingStringInferType().equals(TSDataType.DOUBLE)
            ? TSDataType.DOUBLE
            : TSDataType.FLOAT,
        value);
  }

  /**
   * parse time expression, which is addition and subtraction expression of duration time, now() or
   * DataTimeFormat time.
   *
   * <p>eg. now() + 1d - 2h
   */
  private Long parseDateExpression(IoTDBSqlParser.DateExpressionContext ctx) {
    long time;
    time = parseDateFormat(ctx.getChild(0).getText());
    for (int i = 1; i < ctx.getChildCount(); i = i + 2) {
      if ("+".equals(ctx.getChild(i).getText())) {
        time += DateTimeUtils.convertDurationStrToLong(time, ctx.getChild(i + 1).getText(), false);
      } else {
        time -= DateTimeUtils.convertDurationStrToLong(time, ctx.getChild(i + 1).getText(), false);
      }
    }
    return time;
  }

  private Long parseDateExpression(IoTDBSqlParser.DateExpressionContext ctx, long currentTime) {
    long time;
    time = parseDateFormat(ctx.getChild(0).getText(), currentTime);
    for (int i = 1; i < ctx.getChildCount(); i = i + 2) {
      if ("+".equals(ctx.getChild(i).getText())) {
        time += DateTimeUtils.convertDurationStrToLong(time, ctx.getChild(i + 1).getText(), false);
      } else {
        time -= DateTimeUtils.convertDurationStrToLong(time, ctx.getChild(i + 1).getText(), false);
      }
    }
    return time;
  }

  private long parseTimeValue(IoTDBSqlParser.TimeValueContext ctx, long currentTime) {
    if (ctx.INTEGER_LITERAL() != null) {
      try {
        if (ctx.MINUS() != null) {
          return -Long.parseLong(ctx.INTEGER_LITERAL().getText());
        }
        return Long.parseLong(ctx.INTEGER_LITERAL().getText());
      } catch (NumberFormatException e) {
        throw new SemanticException(
            String.format("Can not parse %s to long value", ctx.INTEGER_LITERAL().getText()));
      }
    } else if (ctx.dateExpression() != null) {
      return parseDateExpression(ctx.dateExpression(), currentTime);
    } else {
      return parseDateFormat(ctx.datetimeLiteral().getText(), currentTime);
    }
  }

  /** Utils. */
  private void setMap(IoTDBSqlParser.AlterClauseContext ctx, Map<String, String> alterMap) {
    List<IoTDBSqlParser.AttributePairContext> tagsList = ctx.attributePair();
    String key;
    if (ctx.attributePair(0) != null) {
      for (IoTDBSqlParser.AttributePairContext attributePair : tagsList) {
        key = parseAttributeKey(attributePair.attributeKey());
        alterMap.computeIfPresent(
            key,
            (k, v) -> {
              throw new SemanticException(
                  String.format("There's duplicate [%s] in tag or attribute clause.", k));
            });
        alterMap.put(key, parseAttributeValue(attributePair.attributeValue()));
      }
    }
  }

  private Map<String, String> extractMap(
      List<IoTDBSqlParser.AttributePairContext> attributePair2,
      IoTDBSqlParser.AttributePairContext attributePair3) {
    Map<String, String> tags = new HashMap<>(attributePair2.size());
    if (attributePair3 != null) {
      String key;
      for (IoTDBSqlParser.AttributePairContext attributePair : attributePair2) {
        key = parseAttributeKey(attributePair.attributeKey());
        tags.computeIfPresent(
            key,
            (k, v) -> {
              throw new SemanticException(
                  String.format("There's duplicate [%s] in tag or attribute clause.", k));
            });
        tags.put(key, parseAttributeValue(attributePair.attributeValue()));
      }
    }
    return tags;
  }

  private String parseAttributeKey(IoTDBSqlParser.AttributeKeyContext ctx) {
    if (ctx.constant() != null) {
      return parseStringLiteral(ctx.getText());
    }
    return parseIdentifier(ctx.getText());
  }

  private String parseAttributeValue(IoTDBSqlParser.AttributeValueContext ctx) {
    if (ctx.constant() != null) {
      return parseStringLiteral(ctx.getText());
    }
    return parseIdentifier(ctx.getText());
  }

  // Merge
  @Override
  public Statement visitMerge(IoTDBSqlParser.MergeContext ctx) {
    MergeStatement mergeStatement = new MergeStatement(StatementType.MERGE);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException("MERGE ON CLUSTER is not supported in standalone mode");
    }
    mergeStatement.setOnCluster(ctx.LOCAL() == null);
    return mergeStatement;
  }

  @Override
  public Statement visitFullMerge(IoTDBSqlParser.FullMergeContext ctx) {
    MergeStatement mergeStatement = new MergeStatement(StatementType.FULL_MERGE);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException("FULL MERGE ON CLUSTER is not supported in standalone mode");
    }
    mergeStatement.setOnCluster(ctx.LOCAL() == null);
    return mergeStatement;
  }

  // Flush

  @Override
  public Statement visitFlush(IoTDBSqlParser.FlushContext ctx) {
    FlushStatement flushStatement = new FlushStatement(StatementType.FLUSH);
    List<PartialPath> storageGroups = null;
    if (ctx.boolean_literal() != null) {
      flushStatement.setSeq(Boolean.parseBoolean(ctx.boolean_literal().getText()));
    }
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException("FLUSH ON CLUSTER is not supported in standalone mode");
    }
    flushStatement.setOnCluster(ctx.LOCAL() == null);
    if (ctx.prefixPath(0) != null) {
      storageGroups = new ArrayList<>();
      for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
        storageGroups.add(parsePrefixPath(prefixPathContext));
      }
    }
    flushStatement.setStorageGroups(storageGroups);
    return flushStatement;
  }

  // Clear Cache

  @Override
  public Statement visitClearCache(IoTDBSqlParser.ClearCacheContext ctx) {
    ClearCacheStatement clearCacheStatement = new ClearCacheStatement(StatementType.CLEAR_CACHE);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException("CLEAR CACHE ON CLUSTER is not supported in standalone mode");
    }
    clearCacheStatement.setOnCluster(ctx.LOCAL() == null);
    return clearCacheStatement;
  }

  // Start Repair Data

  @Override
  public Statement visitStartRepairData(IoTDBSqlParser.StartRepairDataContext ctx) {
    StartRepairDataStatement startRepairDataStatement =
        new StartRepairDataStatement(StatementType.START_REPAIR_DATA);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException(
          "START REPAIR DATA ON CLUSTER is not supported in standalone mode");
    }
    startRepairDataStatement.setOnCluster(ctx.LOCAL() == null);
    return startRepairDataStatement;
  }

  // Stop Repair Data

  @Override
  public Statement visitStopRepairData(IoTDBSqlParser.StopRepairDataContext ctx) {
    StopRepairDataStatement stopRepairDataStatement =
        new StopRepairDataStatement(StatementType.STOP_REPAIR_DATA);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException(
          "STOP REPAIR DATA ON CLUSTER is not supported in standalone mode");
    }
    stopRepairDataStatement.setOnCluster(ctx.LOCAL() == null);
    return stopRepairDataStatement;
  }

  // Load Configuration

  @Override
  public Statement visitLoadConfiguration(IoTDBSqlParser.LoadConfigurationContext ctx) {
    LoadConfigurationStatement loadConfigurationStatement =
        new LoadConfigurationStatement(StatementType.LOAD_CONFIGURATION);
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException(
          "LOAD CONFIGURATION ON CLUSTER is not supported in standalone mode");
    }
    loadConfigurationStatement.setOnCluster(ctx.LOCAL() == null);
    return loadConfigurationStatement;
  }

  // Set System Status

  @Override
  public Statement visitSetSystemStatus(IoTDBSqlParser.SetSystemStatusContext ctx) {
    SetSystemStatusStatement setSystemStatusStatement = new SetSystemStatusStatement();
    if (ctx.CLUSTER() != null && !IoTDBDescriptor.getInstance().getConfig().isClusterMode()) {
      throw new SemanticException(
          "SET SYSTEM STATUS ON CLUSTER is not supported in standalone mode");
    }
    setSystemStatusStatement.setOnCluster(ctx.LOCAL() == null);
    if (ctx.RUNNING() != null) {
      setSystemStatusStatement.setStatus(NodeStatus.Running);
    } else if (ctx.READONLY() != null) {
      setSystemStatusStatement.setStatus(NodeStatus.ReadOnly);
    } else {
      throw new SemanticException("Unknown system status in set system command.");
    }
    return setSystemStatusStatement;
  }

  // Kill Query
  @Override
  public Statement visitKillQuery(IoTDBSqlParser.KillQueryContext ctx) {
    if (ctx.queryId != null) {
      return new KillQueryStatement(parseStringLiteral(ctx.queryId.getText()));
    }
    return new KillQueryStatement();
  }

  // show query processlist

  @Override
  public Statement visitShowQueries(IoTDBSqlParser.ShowQueriesContext ctx) {
    ShowQueriesStatement showQueriesStatement = new ShowQueriesStatement();
    // parse WHERE
    if (ctx.whereClause() != null) {
      showQueriesStatement.setWhereCondition(parseWhereClause(ctx.whereClause()));
    }

    // parse ORDER BY
    if (ctx.orderByClause() != null) {
      showQueriesStatement.setOrderByComponent(
          parseOrderByClause(
              ctx.orderByClause(),
              ImmutableSet.of(
                  OrderByKey.TIME,
                  OrderByKey.QUERYID,
                  OrderByKey.DATANODEID,
                  OrderByKey.ELAPSEDTIME,
                  OrderByKey.STATEMENT)));
    }

    // parse LIMIT & OFFSET
    if (ctx.rowPaginationClause() != null) {
      if (ctx.rowPaginationClause().limitClause() != null) {
        showQueriesStatement.setRowLimit(parseLimitClause(ctx.rowPaginationClause().limitClause()));
      }
      if (ctx.rowPaginationClause().offsetClause() != null) {
        showQueriesStatement.setRowOffset(
            parseOffsetClause(ctx.rowPaginationClause().offsetClause()));
      }
    }

    showQueriesStatement.setZoneId(zoneId);
    return showQueriesStatement;
  }

  // show region

  @Override
  public Statement visitShowRegions(IoTDBSqlParser.ShowRegionsContext ctx) {
    ShowRegionStatement showRegionStatement = new ShowRegionStatement();
    // TODO: Maybe add a show ConfigNode region in the future
    if (ctx.DATA() != null) {
      showRegionStatement.setRegionType(TConsensusGroupType.DataRegion);
    } else if (ctx.SCHEMA() != null) {
      showRegionStatement.setRegionType(TConsensusGroupType.SchemaRegion);
    } else {
      showRegionStatement.setRegionType(null);
    }

    if (ctx.OF() != null) {
      List<PartialPath> storageGroups = null;
      if (ctx.prefixPath(0) != null) {
        storageGroups = new ArrayList<>();
        for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
          storageGroups.add(parsePrefixPath(prefixPathContext));
        }
      }
      showRegionStatement.setStorageGroups(storageGroups);
    } else {
      showRegionStatement.setStorageGroups(null);
    }

    if (ctx.ON() != null) {
      List<Integer> nodeIds = new ArrayList<>();
      for (TerminalNode nodeid : ctx.INTEGER_LITERAL()) {
        nodeIds.add(Integer.parseInt(nodeid.getText()));
      }
      showRegionStatement.setNodeIds(nodeIds);
    } else {
      showRegionStatement.setNodeIds(null);
    }
    return showRegionStatement;
  }

  // show datanodes

  @Override
  public Statement visitShowDataNodes(IoTDBSqlParser.ShowDataNodesContext ctx) {
    return new ShowDataNodesStatement();
  }

  // show confignodes

  @Override
  public Statement visitShowConfigNodes(IoTDBSqlParser.ShowConfigNodesContext ctx) {
    return new ShowConfigNodesStatement();
  }

  // device template

  @Override
  public Statement visitCreateSchemaTemplate(IoTDBSqlParser.CreateSchemaTemplateContext ctx) {
    String name = parseIdentifier(ctx.templateName.getText());
    List<List<String>> measurementsList = new ArrayList<>();
    List<List<TSDataType>> dataTypesList = new ArrayList<>();
    List<List<TSEncoding>> encodingsList = new ArrayList<>();
    List<List<CompressionType>> compressorsList = new ArrayList<>();

    if (ctx.ALIGNED() != null) {
      // aligned
      List<String> measurements = new ArrayList<>();
      List<TSDataType> dataTypes = new ArrayList<>();
      List<TSEncoding> encodings = new ArrayList<>();
      List<CompressionType> compressors = new ArrayList<>();
      for (IoTDBSqlParser.TemplateMeasurementClauseContext templateClauseContext :
          ctx.templateMeasurementClause()) {
        measurements.add(
            parseNodeNameWithoutWildCard(templateClauseContext.nodeNameWithoutWildcard()));
        parseAttributeClauseForSchemaTemplate(
            templateClauseContext.attributeClauses(), dataTypes, encodings, compressors);
      }
      measurementsList.add(measurements);
      dataTypesList.add(dataTypes);
      encodingsList.add(encodings);
      compressorsList.add(compressors);
    } else {
      // non-aligned
      for (IoTDBSqlParser.TemplateMeasurementClauseContext templateClauseContext :
          ctx.templateMeasurementClause()) {
        List<String> measurements = new ArrayList<>();
        List<TSDataType> dataTypes = new ArrayList<>();
        List<TSEncoding> encodings = new ArrayList<>();
        List<CompressionType> compressors = new ArrayList<>();
        measurements.add(
            parseNodeNameWithoutWildCard(templateClauseContext.nodeNameWithoutWildcard()));
        parseAttributeClauseForSchemaTemplate(
            templateClauseContext.attributeClauses(), dataTypes, encodings, compressors);
        measurementsList.add(measurements);
        dataTypesList.add(dataTypes);
        encodingsList.add(encodings);
        compressorsList.add(compressors);
      }
    }

    return new CreateSchemaTemplateStatement(
        name,
        measurementsList,
        dataTypesList,
        encodingsList,
        compressorsList,
        ctx.ALIGNED() != null);
  }

  @Override
  public Statement visitAlterSchemaTemplate(IoTDBSqlParser.AlterSchemaTemplateContext ctx) {
    String name = parseIdentifier(ctx.templateName.getText());
    List<String> measurements = new ArrayList<>();
    List<TSDataType> dataTypes = new ArrayList<>();
    List<TSEncoding> encodings = new ArrayList<>();
    List<CompressionType> compressors = new ArrayList<>();

    for (IoTDBSqlParser.TemplateMeasurementClauseContext templateClauseContext :
        ctx.templateMeasurementClause()) {
      measurements.add(
          parseNodeNameWithoutWildCard(templateClauseContext.nodeNameWithoutWildcard()));
      parseAttributeClauseForSchemaTemplate(
          templateClauseContext.attributeClauses(), dataTypes, encodings, compressors);
    }

    return new AlterSchemaTemplateStatement(
        name,
        measurements,
        dataTypes,
        encodings,
        compressors,
        TemplateAlterOperationType.EXTEND_TEMPLATE);
  }

  void parseAttributeClauseForSchemaTemplate(
      IoTDBSqlParser.AttributeClausesContext ctx,
      List<TSDataType> dataTypes,
      List<TSEncoding> encodings,
      List<CompressionType> compressors) {
    if (ctx.aliasNodeName() != null) {
      throw new SemanticException("Device Template: alias is not supported yet.");
    }

    TSDataType dataType = parseDataTypeAttribute(ctx);
    dataTypes.add(dataType);

    Map<String, String> props = new HashMap<>();
    if (ctx.attributePair() != null) {
      for (int i = 0; i < ctx.attributePair().size(); i++) {
        props.put(
            parseAttributeKey(ctx.attributePair(i).attributeKey()).toLowerCase(),
            parseAttributeValue(ctx.attributePair(i).attributeValue()));
      }
    }

    TSEncoding encoding = IoTDBDescriptor.getInstance().getDefaultEncodingByType(dataType);
    if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase())) {
      String encodingString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase()).toUpperCase();
      try {
        encoding = TSEncoding.valueOf(encodingString);
        encodings.add(encoding);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_ENCODING.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported encoding: %s", encodingString));
      }
    } else {
      encodings.add(encoding);
    }

    CompressionType compressor = TSFileDescriptor.getInstance().getConfig().getCompressor();
    if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase())) {
      String compressorString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase()).toUpperCase();
      try {
        compressor = CompressionType.valueOf(compressorString);
        compressors.add(compressor);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSOR.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported compressor: %s", compressorString));
      }
    } else if (props.containsKey(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase())) {
      String compressionString =
          props.get(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase()).toUpperCase();
      try {
        compressor = CompressionType.valueOf(compressionString);
        compressors.add(compressor);
        props.remove(IoTDBConstant.COLUMN_TIMESERIES_COMPRESSION.toLowerCase());
      } catch (Exception e) {
        throw new SemanticException(
            String.format("Unsupported compression: %s", compressionString));
      }
    } else {
      compressors.add(compressor);
    }

    if (props.size() > 0) {
      throw new SemanticException("Device Template: property is not supported yet.");
    }

    if (ctx.tagClause() != null) {
      throw new SemanticException("Device Template: tag is not supported yet.");
    }

    if (ctx.attributeClause() != null) {
      throw new SemanticException("Device Template: attribute is not supported yet.");
    }
  }

  private TSDataType parseDataTypeAttribute(IoTDBSqlParser.AttributeClausesContext ctx) {
    TSDataType dataType = null;
    if (ctx.dataType != null) {
      if (ctx.attributeKey() != null
          && !parseAttributeKey(ctx.attributeKey())
              .equalsIgnoreCase(IoTDBConstant.COLUMN_TIMESERIES_DATATYPE)) {
        throw new SemanticException("Expecting datatype");
      }
      String dataTypeString = ctx.dataType.getText().toUpperCase();
      try {
        dataType = TSDataType.valueOf(dataTypeString);
      } catch (Exception e) {
        throw new SemanticException(String.format("Unsupported datatype: %s", dataTypeString));
      }
    }
    return dataType;
  }

  @Override
  public Statement visitShowSchemaTemplates(IoTDBSqlParser.ShowSchemaTemplatesContext ctx) {
    return new ShowSchemaTemplateStatement();
  }

  @Override
  public Statement visitShowNodesInSchemaTemplate(
      IoTDBSqlParser.ShowNodesInSchemaTemplateContext ctx) {
    String templateName = parseIdentifier(ctx.templateName.getText());
    return new ShowNodesInSchemaTemplateStatement(templateName);
  }

  @Override
  public Statement visitSetSchemaTemplate(IoTDBSqlParser.SetSchemaTemplateContext ctx) {
    String templateName = parseIdentifier(ctx.templateName.getText());
    return new SetSchemaTemplateStatement(templateName, parsePrefixPath(ctx.prefixPath()));
  }

  @Override
  public Statement visitShowPathsSetSchemaTemplate(
      IoTDBSqlParser.ShowPathsSetSchemaTemplateContext ctx) {
    String templateName = parseIdentifier(ctx.templateName.getText());
    return new ShowPathSetTemplateStatement(templateName);
  }

  @Override
  public Statement visitCreateTimeseriesUsingSchemaTemplate(
      IoTDBSqlParser.CreateTimeseriesUsingSchemaTemplateContext ctx) {
    ActivateTemplateStatement statement = new ActivateTemplateStatement();
    statement.setPath(parsePrefixPath(ctx.prefixPath()));
    return statement;
  }

  @Override
  public Statement visitShowPathsUsingSchemaTemplate(
      IoTDBSqlParser.ShowPathsUsingSchemaTemplateContext ctx) {
    PartialPath pathPattern;
    if (ctx.prefixPath() == null) {
      pathPattern = new PartialPath(SqlConstant.getSingleRootArray());
    } else {
      pathPattern = parsePrefixPath(ctx.prefixPath());
    }
    return new ShowPathsUsingTemplateStatement(
        pathPattern, parseIdentifier(ctx.templateName.getText()));
  }

  @Override
  public Statement visitDropTimeseriesOfSchemaTemplate(
      IoTDBSqlParser.DropTimeseriesOfSchemaTemplateContext ctx) {
    DeactivateTemplateStatement statement = new DeactivateTemplateStatement();
    if (ctx.templateName != null) {
      statement.setTemplateName(parseIdentifier(ctx.templateName.getText()));
    }
    List<PartialPath> pathPatternList = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
      pathPatternList.add(parsePrefixPath(prefixPathContext));
    }
    statement.setPathPatternList(pathPatternList);
    return statement;
  }

  @Override
  public Statement visitUnsetSchemaTemplate(IoTDBSqlParser.UnsetSchemaTemplateContext ctx) {
    String templateName = parseIdentifier(ctx.templateName.getText());
    PartialPath path = parsePrefixPath(ctx.prefixPath());
    return new UnsetSchemaTemplateStatement(templateName, path);
  }

  @Override
  public Statement visitDropSchemaTemplate(IoTDBSqlParser.DropSchemaTemplateContext ctx) {
    return new DropSchemaTemplateStatement(parseIdentifier(ctx.templateName.getText()));
  }

  public Map<String, String> parseSyncAttributeClauses(
      IoTDBSqlParser.SyncAttributeClausesContext ctx) {

    Map<String, String> attributes = new HashMap<>();

    List<IoTDBSqlParser.AttributePairContext> attributePairs = ctx.attributePair();
    if (ctx.attributePair(0) != null) {
      for (IoTDBSqlParser.AttributePairContext attributePair : attributePairs) {
        attributes.put(
            parseAttributeKey(attributePair.attributeKey()).toLowerCase(),
            parseAttributeValue(attributePair.attributeValue()).toLowerCase());
      }
    }

    return attributes;
  }

  // PIPE

  @Override
  public Statement visitCreatePipe(IoTDBSqlParser.CreatePipeContext ctx) {
    final CreatePipeStatement createPipeStatement =
        new CreatePipeStatement(StatementType.CREATE_PIPE);

    if (ctx.pipeName != null) {
      createPipeStatement.setPipeName(ctx.pipeName.getText());
    } else {
      throw new SemanticException(
          "Not support for this sql in CREATEPIPE, please enter pipe name.");
    }
    if (ctx.extractorAttributesClause() != null) {
      createPipeStatement.setExtractorAttributes(
          parseExtractorAttributesClause(ctx.extractorAttributesClause()));
    } else {
      createPipeStatement.setExtractorAttributes(new HashMap<>());
    }
    if (ctx.processorAttributesClause() != null) {
      createPipeStatement.setProcessorAttributes(
          parseProcessorAttributesClause(ctx.processorAttributesClause()));
    } else {
      createPipeStatement.setProcessorAttributes(new HashMap<>());
    }
    createPipeStatement.setConnectorAttributes(
        parseConnectorAttributesClause(ctx.connectorAttributesClause()));
    return createPipeStatement;
  }

  private Map<String, String> parseExtractorAttributesClause(
      IoTDBSqlParser.ExtractorAttributesClauseContext ctx) {
    final Map<String, String> collectorMap = new HashMap<>();
    for (IoTDBSqlParser.ExtractorAttributeClauseContext singleCtx :
        ctx.extractorAttributeClause()) {
      collectorMap.put(
          parseStringLiteral(singleCtx.extractorKey.getText()),
          parseStringLiteral(singleCtx.extractorValue.getText()));
    }
    return collectorMap;
  }

  private Map<String, String> parseProcessorAttributesClause(
      IoTDBSqlParser.ProcessorAttributesClauseContext ctx) {
    final Map<String, String> processorMap = new HashMap<>();
    for (IoTDBSqlParser.ProcessorAttributeClauseContext singleCtx :
        ctx.processorAttributeClause()) {
      processorMap.put(
          parseStringLiteral(singleCtx.processorKey.getText()),
          parseStringLiteral(singleCtx.processorValue.getText()));
    }
    return processorMap;
  }

  private Map<String, String> parseConnectorAttributesClause(
      IoTDBSqlParser.ConnectorAttributesClauseContext ctx) {
    final Map<String, String> connectorMap = new HashMap<>();
    for (IoTDBSqlParser.ConnectorAttributeClauseContext singleCtx :
        ctx.connectorAttributeClause()) {
      connectorMap.put(
          parseStringLiteral(singleCtx.connectorKey.getText()),
          parseStringLiteral(singleCtx.connectorValue.getText()));
    }
    return connectorMap;
  }

  @Override
  public Statement visitDropPipe(IoTDBSqlParser.DropPipeContext ctx) {
    final DropPipeStatement dropPipeStatement = new DropPipeStatement(StatementType.DROP_PIPE);

    if (ctx.pipeName != null) {
      dropPipeStatement.setPipeName(ctx.pipeName.getText());
    } else {
      throw new SemanticException("Not support for this sql in DROP PIPE, please enter pipename.");
    }

    return dropPipeStatement;
  }

  @Override
  public Statement visitStartPipe(IoTDBSqlParser.StartPipeContext ctx) {
    final StartPipeStatement startPipeStatement = new StartPipeStatement(StatementType.START_PIPE);

    if (ctx.pipeName != null) {
      startPipeStatement.setPipeName(ctx.pipeName.getText());
    } else {
      throw new SemanticException("Not support for this sql in START PIPE, please enter pipename.");
    }

    return startPipeStatement;
  }

  @Override
  public Statement visitStopPipe(IoTDBSqlParser.StopPipeContext ctx) {
    final StopPipeStatement stopPipeStatement = new StopPipeStatement(StatementType.STOP_PIPE);

    if (ctx.pipeName != null) {
      stopPipeStatement.setPipeName(ctx.pipeName.getText());
    } else {
      throw new SemanticException("Not support for this sql in STOP PIPE, please enter pipename.");
    }

    return stopPipeStatement;
  }

  @Override
  public Statement visitShowPipes(IoTDBSqlParser.ShowPipesContext ctx) {
    final ShowPipesStatement showPipesStatement = new ShowPipesStatement();

    if (ctx.pipeName != null) {
      showPipesStatement.setPipeName(parseIdentifier(ctx.pipeName.getText()));
    }
    showPipesStatement.setWhereClause(ctx.CONNECTOR() != null);

    return showPipesStatement;
  }

  @Override
  public Statement visitGetRegionId(IoTDBSqlParser.GetRegionIdContext ctx) {
    TConsensusGroupType type =
        ctx.DATA() == null ? TConsensusGroupType.SchemaRegion : TConsensusGroupType.DataRegion;
    GetRegionIdStatement getRegionIdStatement = new GetRegionIdStatement(type);
    if (ctx.database != null) {
      getRegionIdStatement.setDatabase(ctx.database.getText());
    } else {
      getRegionIdStatement.setDevice(ctx.device.getText());
    }
    getRegionIdStatement.setStartTimeStamp(-1L);
    getRegionIdStatement.setEndTimeStamp(Long.MAX_VALUE);

    if (ctx.timeRangeExpression != null) {
      Expression timeRangeExpression = parseExpression(ctx.timeRangeExpression, true);
      getRegionIdStatement = parseTimeRangeExpression(timeRangeExpression, getRegionIdStatement);
    }

    return getRegionIdStatement;
  }

  public GetRegionIdStatement parseTimeRangeExpression(
      Expression timeRangeExpression, GetRegionIdStatement getRegionIdStatement) {
    List<Expression> result = timeRangeExpression.getExpressions();
    if (timeRangeExpression.getExpressionType() == ExpressionType.LOGIC_AND) {
      getRegionIdStatement = parseTimeRangeExpression(result.get(0), getRegionIdStatement);
      getRegionIdStatement = parseTimeRangeExpression(result.get(1), getRegionIdStatement);
    } else if (result.get(0).getExpressionType() == ExpressionType.TIMESTAMP
        && result.get(1) instanceof ConstantOperand
        && ((ConstantOperand) result.get(1)).getDataType() == TSDataType.INT64) {
      ExpressionType tmpType = timeRangeExpression.getExpressionType();
      long timestamp = Long.parseLong(((ConstantOperand) result.get(1)).getValueString());
      switch (tmpType) {
        case EQUAL_TO:
          getRegionIdStatement.setStartTimeStamp(
              Math.max(getRegionIdStatement.getStartTimeStamp(), timestamp));
          getRegionIdStatement.setEndTimeStamp(
              Math.min(getRegionIdStatement.getEndTimeStamp(), timestamp));
          break;
        case GREATER_EQUAL:
          getRegionIdStatement.setStartTimeStamp(
              Math.max(getRegionIdStatement.getStartTimeStamp(), timestamp));
          break;
        case GREATER_THAN:
          getRegionIdStatement.setStartTimeStamp(
              Math.max(getRegionIdStatement.getStartTimeStamp(), timestamp + 1));
          break;
        case LESS_EQUAL:
          getRegionIdStatement.setEndTimeStamp(
              Math.min(getRegionIdStatement.getEndTimeStamp(), timestamp));
          break;
        case LESS_THAN:
          getRegionIdStatement.setEndTimeStamp(
              Math.min(getRegionIdStatement.getEndTimeStamp(), timestamp - 1));
          break;
        default:
          throw new UnsupportedOperationException();
      }
    } else {
      throw new SemanticException("Get region id statement‘ expression must be a time expression");
    }
    return getRegionIdStatement;
  }

  @Override
  public Statement visitGetSeriesSlotList(IoTDBSqlParser.GetSeriesSlotListContext ctx) {
    TConsensusGroupType type =
        ctx.DATA() == null ? TConsensusGroupType.SchemaRegion : TConsensusGroupType.DataRegion;
    return new GetSeriesSlotListStatement(ctx.database.getText(), type);
  }

  @Override
  public Statement visitGetTimeSlotList(IoTDBSqlParser.GetTimeSlotListContext ctx) {
    GetTimeSlotListStatement getTimeSlotListStatement = new GetTimeSlotListStatement();
    if (ctx.database != null) {
      getTimeSlotListStatement.setDatabase(ctx.database.getText());
    } else if (ctx.device != null) {
      getTimeSlotListStatement.setDevice(ctx.device.getText());
    } else if (ctx.regionId != null) {
      getTimeSlotListStatement.setRegionId(Integer.parseInt(ctx.regionId.getText()));
    }
    if (ctx.startTime != null) {
      long timestamp = parseTimeValue(ctx.startTime, CommonDateTimeUtils.currentTime());
      getTimeSlotListStatement.setStartTime(timestamp);
    }
    if (ctx.endTime != null) {
      long timestamp = parseTimeValue(ctx.endTime, CommonDateTimeUtils.currentTime());
      getTimeSlotListStatement.setEndTime(timestamp);
    }
    return getTimeSlotListStatement;
  }

  @Override
  public Statement visitCountTimeSlotList(IoTDBSqlParser.CountTimeSlotListContext ctx) {
    CountTimeSlotListStatement countTimeSlotListStatement = new CountTimeSlotListStatement();
    if (ctx.database != null) {
      countTimeSlotListStatement.setDatabase(ctx.database.getText());
    } else if (ctx.device != null) {
      countTimeSlotListStatement.setDevice(ctx.device.getText());
    } else if (ctx.regionId != null) {
      countTimeSlotListStatement.setRegionId(Integer.parseInt(ctx.regionId.getText()));
    }
    if (ctx.startTime != null) {
      countTimeSlotListStatement.setStartTime(Long.parseLong(ctx.startTime.getText()));
    }
    if (ctx.endTime != null) {
      countTimeSlotListStatement.setEndTime(Long.parseLong(ctx.endTime.getText()));
    }
    return countTimeSlotListStatement;
  }

  @Override
  public Statement visitMigrateRegion(IoTDBSqlParser.MigrateRegionContext ctx) {
    return new MigrateRegionStatement(
        Integer.parseInt(ctx.regionId.getText()),
        Integer.parseInt(ctx.fromId.getText()),
        Integer.parseInt(ctx.toId.getText()));
  }

  // Quota
  @Override
  public Statement visitSetSpaceQuota(IoTDBSqlParser.SetSpaceQuotaContext ctx) {
    if (!IoTDBDescriptor.getInstance().getConfig().isQuotaEnable()) {
      throw new SemanticException(LIMIT_CONFIGURATION_ENABLED_ERROR_MSG);
    }
    SetSpaceQuotaStatement setSpaceQuotaStatement = new SetSpaceQuotaStatement();
    List<IoTDBSqlParser.PrefixPathContext> prefixPathContexts = ctx.prefixPath();
    List<String> paths = new ArrayList<>();
    for (IoTDBSqlParser.PrefixPathContext prefixPathContext : prefixPathContexts) {
      paths.add(parsePrefixPath(prefixPathContext).getFullPath());
    }
    setSpaceQuotaStatement.setPrefixPathList(paths);

    Map<String, String> quotas = new HashMap<>();
    for (IoTDBSqlParser.AttributePairContext attributePair : ctx.attributePair()) {
      quotas.put(
          parseAttributeKey(attributePair.attributeKey()),
          parseAttributeValue(attributePair.attributeValue()));
    }

    quotas
        .keySet()
        .forEach(
            quotaType -> {
              switch (quotaType) {
                case IoTDBConstant.COLUMN_DEVICES:
                  break;
                case IoTDBConstant.COLUMN_TIMESERIES:
                  break;
                case IoTDBConstant.SPACE_QUOTA_DISK:
                  break;
                default:
                  throw new SemanticException("Wrong space quota type: " + quotaType);
              }
            });

    if (quotas.containsKey(IoTDBConstant.COLUMN_DEVICES)) {
      if (quotas.get(IoTDBConstant.COLUMN_DEVICES).equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        setSpaceQuotaStatement.setDeviceNum(IoTDBConstant.UNLIMITED_VALUE);
      } else if (Long.parseLong(quotas.get(IoTDBConstant.COLUMN_DEVICES)) <= 0) {
        throw new SemanticException("Please set the number of devices greater than 0");
      } else {
        setSpaceQuotaStatement.setDeviceNum(
            Long.parseLong(quotas.get(IoTDBConstant.COLUMN_DEVICES)));
      }
    }
    if (quotas.containsKey(IoTDBConstant.COLUMN_TIMESERIES)) {
      if (quotas.get(IoTDBConstant.COLUMN_TIMESERIES).equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        setSpaceQuotaStatement.setTimeSeriesNum(IoTDBConstant.UNLIMITED_VALUE);
      } else if (Long.parseLong(quotas.get(IoTDBConstant.COLUMN_TIMESERIES)) <= 0) {
        throw new SemanticException("Please set the number of timeseries greater than 0");
      } else {
        setSpaceQuotaStatement.setTimeSeriesNum(
            Long.parseLong(quotas.get(IoTDBConstant.COLUMN_TIMESERIES)));
      }
    }
    if (quotas.containsKey(IoTDBConstant.SPACE_QUOTA_DISK)) {
      if (quotas.get(IoTDBConstant.SPACE_QUOTA_DISK).equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        setSpaceQuotaStatement.setDiskSize(IoTDBConstant.UNLIMITED_VALUE);
      } else {
        setSpaceQuotaStatement.setDiskSize(
            parseSpaceQuotaSizeUnit(quotas.get(IoTDBConstant.SPACE_QUOTA_DISK)));
      }
    }
    return setSpaceQuotaStatement;
  }

  @Override
  public Statement visitSetThrottleQuota(IoTDBSqlParser.SetThrottleQuotaContext ctx) {
    if (!IoTDBDescriptor.getInstance().getConfig().isQuotaEnable()) {
      throw new SemanticException(LIMIT_CONFIGURATION_ENABLED_ERROR_MSG);
    }
    if (parseIdentifier(ctx.userName.getText()).equals(IoTDBConstant.PATH_ROOT)) {
      throw new SemanticException("Cannot set throttle quota for user root.");
    }
    SetThrottleQuotaStatement setThrottleQuotaStatement = new SetThrottleQuotaStatement();
    setThrottleQuotaStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    Map<String, String> quotas = new HashMap<>();
    Map<ThrottleType, TTimedQuota> throttleLimit = new HashMap<>();
    for (IoTDBSqlParser.AttributePairContext attributePair : ctx.attributePair()) {
      quotas.put(
          parseAttributeKey(attributePair.attributeKey()),
          parseAttributeValue(attributePair.attributeValue()));
    }
    if (quotas.containsKey(IoTDBConstant.REQUEST_NUM_PER_UNIT_TIME)) {
      TTimedQuota timedQuota;
      String request = quotas.get(IoTDBConstant.REQUEST_NUM_PER_UNIT_TIME);
      if (request.equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        timedQuota = new TTimedQuota(IoTDBConstant.SEC, Long.MAX_VALUE);
      } else {
        String[] split = request.toLowerCase().split(IoTDBConstant.REQ_SPLIT_UNIT);
        if (Long.parseLong(split[0]) < 0) {
          throw new SemanticException("Please set the number of requests greater than 0");
        }
        timedQuota =
            new TTimedQuota(parseThrottleQuotaTimeUnit(split[1]), Long.parseLong(split[0]));
      }
      if (quotas.get(IoTDBConstant.REQUEST_TYPE) == null) {
        throttleLimit.put(ThrottleType.REQUEST_NUMBER, timedQuota);
      } else {
        switch (quotas.get(IoTDBConstant.REQUEST_TYPE)) {
          case IoTDBConstant.REQUEST_TYPE_READ:
            throttleLimit.put(ThrottleType.READ_NUMBER, timedQuota);
            break;
          case IoTDBConstant.REQUEST_TYPE_WRITE:
            throttleLimit.put(ThrottleType.WRITE_NUMBER, timedQuota);
            break;
          default:
            throw new SemanticException(
                "Please set the correct request type: " + quotas.get(IoTDBConstant.REQUEST_TYPE));
        }
      }
    }

    if (quotas.containsKey(IoTDBConstant.REQUEST_SIZE_PER_UNIT_TIME)) {
      TTimedQuota timedQuota;
      String size = quotas.get(IoTDBConstant.REQUEST_SIZE_PER_UNIT_TIME);
      if (size.equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        timedQuota = new TTimedQuota(IoTDBConstant.SEC, Long.MAX_VALUE);
      } else {
        String[] split = size.toLowerCase().split("/");
        timedQuota =
            new TTimedQuota(
                parseThrottleQuotaTimeUnit(split[1]), parseThrottleQuotaSizeUnit(split[0]));
      }
      if (quotas.get(IoTDBConstant.REQUEST_TYPE) == null) {
        throttleLimit.put(ThrottleType.REQUEST_SIZE, timedQuota);
      } else {
        switch (quotas.get(IoTDBConstant.REQUEST_TYPE)) {
          case IoTDBConstant.REQUEST_TYPE_READ:
            throttleLimit.put(ThrottleType.READ_SIZE, timedQuota);
            break;
          case IoTDBConstant.REQUEST_TYPE_WRITE:
            throttleLimit.put(ThrottleType.WRITE_SIZE, timedQuota);
            break;
          default:
            throw new SemanticException(
                "Please set the correct request type: " + quotas.get(IoTDBConstant.REQUEST_TYPE));
        }
      }
    }

    if (quotas.containsKey(IoTDBConstant.MEMORY_SIZE_PER_READ)) {
      String mem = quotas.get(IoTDBConstant.MEMORY_SIZE_PER_READ);
      if (mem.equals(IoTDBConstant.QUOTA_UNLIMITED)) {
        setThrottleQuotaStatement.setMemLimit(IoTDBConstant.UNLIMITED_VALUE);
      } else {
        setThrottleQuotaStatement.setMemLimit(parseThrottleQuotaSizeUnit(mem));
      }
    }

    if (quotas.containsKey(IoTDBConstant.CPU_NUMBER_PER_READ)) {
      String cpuLimit = quotas.get(IoTDBConstant.CPU_NUMBER_PER_READ);
      if (cpuLimit.contains(IoTDBConstant.QUOTA_UNLIMITED)) {
        setThrottleQuotaStatement.setCpuLimit(IoTDBConstant.UNLIMITED_VALUE);
      } else {
        int cpuNum = Integer.parseInt(cpuLimit);
        if (cpuNum <= 0) {
          throw new SemanticException("Please set the number of cpu greater than 0");
        }
        setThrottleQuotaStatement.setCpuLimit(cpuNum);
      }
    }
    setThrottleQuotaStatement.setThrottleLimit(throttleLimit);
    return setThrottleQuotaStatement;
  }

  @Override
  public Statement visitShowThrottleQuota(IoTDBSqlParser.ShowThrottleQuotaContext ctx) {
    if (!IoTDBDescriptor.getInstance().getConfig().isQuotaEnable()) {
      throw new SemanticException(LIMIT_CONFIGURATION_ENABLED_ERROR_MSG);
    }
    ShowThrottleQuotaStatement showThrottleQuotaStatement = new ShowThrottleQuotaStatement();
    if (ctx.userName != null) {
      showThrottleQuotaStatement.setUserName(parseIdentifier(ctx.userName.getText()));
    }
    return showThrottleQuotaStatement;
  }

  private long parseThrottleQuotaTimeUnit(String timeUnit) {
    switch (timeUnit.toLowerCase()) {
      case IoTDBConstant.SEC_UNIT:
        return IoTDBConstant.SEC;
      case IoTDBConstant.MIN_UNIT:
        return IoTDBConstant.MIN;
      case IoTDBConstant.HOUR_UNIT:
        return IoTDBConstant.HOUR;
      case IoTDBConstant.DAY_UNIT:
        return IoTDBConstant.DAY;
      default:
        throw new SemanticException(
            "When setting the request, the unit is incorrect. Please use 'sec', 'min', 'hour', 'day' as the unit");
    }
  }

  private long parseThrottleQuotaSizeUnit(String data) {
    String unit = data.substring(data.length() - 1);
    long size = Long.parseLong(data.substring(0, data.length() - 1));
    if (size <= 0) {
      throw new SemanticException("Please set the size greater than 0");
    }
    switch (unit.toUpperCase()) {
      case IoTDBConstant.B_UNIT:
        return size;
      case IoTDBConstant.KB_UNIT:
        return size * IoTDBConstant.KB;
      case IoTDBConstant.MB_UNIT:
        return size * IoTDBConstant.MB;
      case IoTDBConstant.GB_UNIT:
        return size * IoTDBConstant.GB;
      case IoTDBConstant.TB_UNIT:
        return size * IoTDBConstant.TB;
      case IoTDBConstant.PB_UNIT:
        return size * IoTDBConstant.PB;
      default:
        throw new SemanticException(
            "When setting the size/time, the unit is incorrect. Please use 'B', 'K', 'M', 'G', 'P', 'T' as the unit");
    }
  }

  private long parseSpaceQuotaSizeUnit(String data) {
    String unit = data.substring(data.length() - 1);
    long disk = Long.parseLong(data.substring(0, data.length() - 1));
    if (disk <= 0) {
      throw new SemanticException("Please set the disk size greater than 0");
    }
    switch (unit.toUpperCase()) {
      case IoTDBConstant.MB_UNIT:
        return disk;
      case IoTDBConstant.GB_UNIT:
        return disk * IoTDBConstant.KB;
      case IoTDBConstant.TB_UNIT:
        return disk * IoTDBConstant.MB;
      case IoTDBConstant.PB_UNIT:
        return disk * IoTDBConstant.GB;
      default:
        throw new SemanticException(
            "When setting the disk size, the unit is incorrect. Please use 'M', 'G', 'P', 'T' as the unit");
    }
  }

  @Override
  public Statement visitShowSpaceQuota(IoTDBSqlParser.ShowSpaceQuotaContext ctx) {
    if (!IoTDBDescriptor.getInstance().getConfig().isQuotaEnable()) {
      throw new SemanticException(LIMIT_CONFIGURATION_ENABLED_ERROR_MSG);
    }
    ShowSpaceQuotaStatement showSpaceQuotaStatement = new ShowSpaceQuotaStatement();
    if (ctx.prefixPath() != null) {
      List<PartialPath> databases = new ArrayList<>();
      for (IoTDBSqlParser.PrefixPathContext prefixPathContext : ctx.prefixPath()) {
        databases.add(parsePrefixPath(prefixPathContext));
      }
      showSpaceQuotaStatement.setDatabases(databases);
    } else {
      showSpaceQuotaStatement.setDatabases(null);
    }
    return showSpaceQuotaStatement;
  }

  @Override
  public Statement visitShowCurrentTimestamp(IoTDBSqlParser.ShowCurrentTimestampContext ctx) {
    return new ShowCurrentTimestampStatement();
  }
}
