From 625269b8a5bb3e3f3d5cdb82fcb642a7c3c4e9a2 Mon Sep 17 00:00:00 2001 From: Jackie Tien Date: Tue, 18 Jun 2024 19:37:24 +0800 Subject: [PATCH] Support for group by time sql paser --- .../relational/it/schema/IoTDBDatabaseIT.java | 37 ++-- .../thrift/impl/ClientRPCServiceImpl.java | 2 +- .../queryengine/plan/parser/ASTVisitor.java | 8 +- .../relational/metadata/MetadataUtil.java | 2 +- .../plan/relational/sql/ast/AstVisitor.java | 4 + .../plan/relational/sql/ast/GroupByTime.java | 145 ++++++++++++++ .../relational/sql/ast/SimpleGroupBy.java | 2 +- .../plan/relational/sql/ast/TimeRange.java | 95 +++++++++ .../relational/sql/parser/AstBuilder.java | 182 ++++++++++++------ .../plan/relational/sql/parser/SqlParser.java | 52 +++-- .../sql/util/ReservedIdentifiers.java | 3 +- .../apache/iotdb/db/utils/DateTimeUtils.java | 79 ++++++-- .../relational/analyzer/AnalyzerTest.java | 2 +- .../relational/grammar/sql/RelationalSql.g4 | 13 +- 14 files changed, 487 insertions(+), 139 deletions(-) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/GroupByTime.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TimeRange.java diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java index cb02608e4249..d2450039a707 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java @@ -117,42 +117,39 @@ public void testManageDatabase() { // create with strange name try { statement.execute("create database 1test"); - fail("create database 1test shouldn't succeed because test1 doesn't exist"); + fail( + "create database 1test shouldn't succeed because 1test is not a legal identifier; identifiers must not start with a digit; surround the identifier with double quotes"); } catch (SQLException e) { - assertTrue( - e.getMessage(), - e.getMessage() - .endsWith( - "identifiers must not start with a digit; surround the identifier with double quotes")); + assertTrue(e.getMessage(), e.getMessage().contains("mismatched input '1'")); } statement.execute("create database \"1test\""); statement.execute("use \"1test\""); statement.execute("drop database \"1test\""); - // try { - // statement.execute("create database 1"); - // fail("create database 1test shouldn't succeed because test1 doesn't exist"); - // } catch (SQLException e) { - // // TODO add error msg assert - // } + try { + statement.execute("create database 1"); + fail("create database 1 shouldn't succeed because 1 is not a legal identifier"); + } catch (SQLException e) { + assertTrue(e.getMessage(), e.getMessage().contains("mismatched input '1'")); + } // // // TODO fix it, should succeed // statement.execute("create database \"1\""); // statement.execute("use \"1\""); // statement.execute("drop database \"1\""); // - // try { - // statement.execute("create database a.b"); - // fail("create database 1test shouldn't succeed because test1 doesn't exist"); - // } catch (SQLException e) { - // // TODO add error msg assert - // } + try { + statement.execute("create database a.b"); + fail("create database a.b shouldn't succeed because a.b is not a legal identifier"); + } catch (SQLException e) { + assertTrue(e.getMessage(), e.getMessage().contains("mismatched input '.'")); + } // // // TODO fix it, should succeed - // statement.execute("create database \"a.b\""); + statement.execute("create database \"a.b\""); // statement.execute("use \"a.b\""); - // statement.execute("drop database \"a.b\""); + statement.execute("drop database \"a.b\""); } catch (SQLException e) { e.printStackTrace(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java index 77bb797ba50f..136ba340e44d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java @@ -337,7 +337,7 @@ private TSExecuteStatementResp executeStatementInternal( req.getTimeout()); } else { org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Statement s = - relationSqlParser.createStatement(statement); + relationSqlParser.createStatement(statement, clientSession.getZoneId()); if (s == null) { return RpcUtils.getTSExecuteStatementResp( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java index db6ae15eed84..34fb4814fdcd 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java @@ -2158,8 +2158,8 @@ public long parseDateTimeFormat(String timestampStr) { } } - public long parseDateTimeFormat(String timestampStr, long currentTime) { - if (timestampStr == null || "".equals(timestampStr.trim())) { + public static long parseDateTimeFormat(String timestampStr, long currentTime, ZoneId zoneId) { + if (timestampStr == null || timestampStr.trim().isEmpty()) { throw new SemanticException("input timestamp cannot be empty"); } if (timestampStr.equalsIgnoreCase(SqlConstant.NOW_FUNC)) { @@ -3138,7 +3138,7 @@ public Long parseDateExpression(IoTDBSqlParser.DateExpressionContext ctx, String private Long parseDateExpression(IoTDBSqlParser.DateExpressionContext ctx, long currentTime) { long time; - time = parseDateTimeFormat(ctx.getChild(0).getText(), currentTime); + time = parseDateTimeFormat(ctx.getChild(0).getText(), currentTime, zoneId); 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); @@ -3163,7 +3163,7 @@ private long parseTimeValue(IoTDBSqlParser.TimeValueContext ctx, long currentTim } else if (ctx.dateExpression() != null) { return parseDateExpression(ctx.dateExpression(), currentTime); } else { - return parseDateTimeFormat(ctx.datetimeLiteral().getText(), currentTime); + return parseDateTimeFormat(ctx.datetimeLiteral().getText(), currentTime, zoneId); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java index d625807b57e1..b97141817b09 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java @@ -84,7 +84,7 @@ public static QualifiedObjectName createQualifiedObjectName( .orElseThrow( () -> new SemanticException( - "Catalog must be specified when session catalog is not set")); + "Database must be specified when session database is not set")); return new QualifiedObjectName(databaseName, objectName); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java index 26cbe300c2ac..98b9f92f341c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java @@ -391,6 +391,10 @@ protected R visitGroupingElement(GroupingElement node, C context) { return visitNode(node, context); } + protected R visitGroupByTime(GroupByTime node, C context) { + return visitGroupingElement(node, context); + } + protected R visitGroupingSets(GroupingSets node, C context) { return visitGroupingElement(node, context); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/GroupByTime.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/GroupByTime.java new file mode 100644 index 000000000000..2d4068e6c27f --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/GroupByTime.java @@ -0,0 +1,145 @@ +/* + * 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.relational.sql.ast; + +import org.apache.tsfile.utils.TimeDuration; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class GroupByTime extends GroupingElement { + + // [startTime, endTime) + private final long startTime; + private final long endTime; + // time interval + private final TimeDuration interval; + // sliding step + private final TimeDuration slidingStep; + // if it is left close and right open interval + private final boolean leftCRightO; + + public GroupByTime( + long startTime, + long endTime, + TimeDuration interval, + TimeDuration slidingStep, + boolean leftCRightO) { + super(null); + this.startTime = startTime; + this.endTime = endTime; + this.interval = interval; + this.slidingStep = slidingStep; + this.leftCRightO = leftCRightO; + } + + public GroupByTime( + NodeLocation location, + long startTime, + long endTime, + TimeDuration interval, + TimeDuration slidingStep, + boolean leftCRightO) { + super(location); + this.startTime = startTime; + this.endTime = endTime; + this.interval = interval; + this.slidingStep = slidingStep; + this.leftCRightO = leftCRightO; + } + + @Override + public List getExpressions() { + return Collections.emptyList(); + } + + @Override + protected R accept(AstVisitor visitor, C context) { + return visitor.visitGroupByTime(this, context); + } + + @Override + public List getChildren() { + return Collections.emptyList(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GroupByTime that = (GroupByTime) o; + return startTime == that.startTime + && endTime == that.endTime + && leftCRightO == that.leftCRightO + && Objects.equals(interval, that.interval) + && Objects.equals(slidingStep, that.slidingStep); + } + + @Override + public int hashCode() { + return Objects.hash(startTime, endTime, interval, slidingStep, leftCRightO); + } + + @Override + public String toString() { + return "GroupByTime{" + + "startTime=" + + startTime + + ", endTime=" + + endTime + + ", interval=" + + interval + + ", slidingStep=" + + slidingStep + + ", leftCRightO=" + + leftCRightO + + '}'; + } + + @Override + public boolean shallowEquals(Node other) { + return sameClass(this, other); + } + + public long getStartTime() { + return startTime; + } + + public long getEndTime() { + return endTime; + } + + public TimeDuration getInterval() { + return interval; + } + + public TimeDuration getSlidingStep() { + return slidingStep; + } + + public boolean isLeftCRightO() { + return leftCRightO; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SimpleGroupBy.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SimpleGroupBy.java index 88193960f7de..88b80a6072b7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SimpleGroupBy.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SimpleGroupBy.java @@ -57,7 +57,7 @@ protected R accept(AstVisitor visitor, C context) { @Override public List getChildren() { - return columns; + return getExpressions(); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TimeRange.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TimeRange.java new file mode 100644 index 000000000000..c1fe213ff14b --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TimeRange.java @@ -0,0 +1,95 @@ +/* + * 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.relational.sql.ast; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class TimeRange extends Node { + + // [startTime, endTime) + private final long startTime; + private final long endTime; + // if it is left close and right open interval + private final boolean leftCRightO; + + public TimeRange(long startTime, long endTime, boolean leftCRightO) { + super(null); + this.startTime = startTime; + this.endTime = endTime; + this.leftCRightO = leftCRightO; + } + + public TimeRange(NodeLocation location, long startTime, long endTime, boolean leftCRightO) { + super(location); + this.startTime = startTime; + this.endTime = endTime; + this.leftCRightO = leftCRightO; + } + + @Override + public List getChildren() { + return Collections.emptyList(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeRange timeRange = (TimeRange) o; + return startTime == timeRange.startTime + && endTime == timeRange.endTime + && leftCRightO == timeRange.leftCRightO; + } + + @Override + public int hashCode() { + return Objects.hash(startTime, endTime, leftCRightO); + } + + @Override + public String toString() { + return "TimeRange{" + + "startTime=" + + startTime + + ", endTime=" + + endTime + + ", leftCRightO=" + + leftCRightO + + '}'; + } + + public long getStartTime() { + return startTime; + } + + public long getEndTime() { + return endTime; + } + + public boolean isLeftCRightO() { + return leftCRightO; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java index 52fa55f87be5..ef2a3dfe5e86 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java @@ -20,6 +20,8 @@ package org.apache.iotdb.db.queryengine.plan.relational.sql.parser; import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; +import org.apache.iotdb.commons.utils.CommonDateTimeUtils; +import org.apache.iotdb.db.exception.sql.SemanticException; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AddColumn; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AliasedRelation; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AllColumns; @@ -56,6 +58,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericDataType; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupBy; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupByTime; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingElement; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingSets; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Identifier; @@ -110,6 +113,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SubqueryExpression; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Table; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TableSubquery; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TimeRange; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Trim; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TypeParameter; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Union; @@ -123,15 +127,18 @@ import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlBaseVisitor; import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlLexer; import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlParser; +import org.apache.iotdb.db.utils.DateTimeUtils; import com.google.common.collect.ImmutableList; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.tsfile.utils.TimeDuration; import javax.annotation.Nullable; +import java.time.ZoneId; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -147,6 +154,7 @@ import static org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.ID; import static org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.MEASUREMENT; import static org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory.TIME; +import static org.apache.iotdb.db.queryengine.plan.parser.ASTVisitor.parseDateTimeFormat; import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingSets.Type.CUBE; import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingSets.Type.EXPLICIT; import static org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingSets.Type.ROLLUP; @@ -157,8 +165,11 @@ public class AstBuilder extends RelationalSqlBaseVisitor { @Nullable private final NodeLocation baseLocation; - AstBuilder(@Nullable NodeLocation baseLocation) { + private final ZoneId zoneId; + + AstBuilder(@Nullable NodeLocation baseLocation, ZoneId zoneId) { this.baseLocation = baseLocation; + this.zoneId = zoneId; } @Override @@ -685,6 +696,115 @@ public Node visitGroupBy(RelationalSqlParser.GroupByContext ctx) { visit(ctx.groupingElement(), GroupingElement.class)); } + @Override + public Node visitTimenGrouping(RelationalSqlParser.TimenGroupingContext ctx) { + long startTime = 0; + long endTime = 0; + boolean leftCRightO = true; + if (ctx.timeRange() != null) { + TimeRange timeRange = (TimeRange) visit(ctx.timeRange()); + startTime = timeRange.getStartTime(); + endTime = timeRange.getEndTime(); + leftCRightO = timeRange.isLeftCRightO(); + } + // Parse time interval + TimeDuration interval = DateTimeUtils.constructTimeDuration(ctx.windowInterval.getText()); + TimeDuration slidingStep = interval; + if (ctx.windowStep != null) { + slidingStep = DateTimeUtils.constructTimeDuration(ctx.windowStep.getText()); + } + + if (interval.monthDuration <= 0 && interval.nonMonthDuration <= 0) { + throw new SemanticException( + "The second parameter time interval should be a positive integer."); + } + + if (slidingStep.monthDuration <= 0 && slidingStep.nonMonthDuration <= 0) { + throw new SemanticException( + "The third parameter time slidingStep should be a positive integer."); + } + return new GroupByTime( + getLocation(ctx), startTime, endTime, interval, slidingStep, leftCRightO); + } + + @Override + public Node visitLeftClosedRightOpen(RelationalSqlParser.LeftClosedRightOpenContext ctx) { + return getTimeRange(ctx.timeValue(0), ctx.timeValue(1), true); + } + + @Override + public Node visitLeftOpenRightClosed(RelationalSqlParser.LeftOpenRightClosedContext ctx) { + return getTimeRange(ctx.timeValue(0), ctx.timeValue(1), false); + } + + private TimeRange getTimeRange( + RelationalSqlParser.TimeValueContext left, + RelationalSqlParser.TimeValueContext right, + boolean leftCRightO) { + long currentTime = CommonDateTimeUtils.currentTime(); + long startTime = parseTimeValue(left, currentTime); + long endTime = parseTimeValue(right, currentTime); + if (startTime >= endTime) { + throw new SemanticException("Start time should be smaller than endTime in GroupBy"); + } + return new TimeRange(startTime, endTime, leftCRightO); + } + + private long parseTimeValue(RelationalSqlParser.TimeValueContext ctx, long currentTime) { + if (ctx.INTEGER_VALUE() != null) { + try { + if (ctx.MINUS() != null) { + return -Long.parseLong(ctx.INTEGER_VALUE().getText()); + } + return Long.parseLong(ctx.INTEGER_VALUE().getText()); + } catch (NumberFormatException e) { + throw new SemanticException( + String.format("Can not parse %s to long value", ctx.INTEGER_VALUE().getText())); + } + } else { + return parseDateExpression(ctx.dateExpression(), currentTime); + } + } + + private Long parseDateExpression( + RelationalSqlParser.DateExpressionContext ctx, long currentTime) { + long time; + time = parseDateTimeFormat(ctx.getChild(0).getText(), currentTime, zoneId); + 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; + } + + @Override + public Node visitVariationGrouping(RelationalSqlParser.VariationGroupingContext ctx) { + return super.visitVariationGrouping(ctx); + } + + @Override + public Node visitConditionGrouping(RelationalSqlParser.ConditionGroupingContext ctx) { + return super.visitConditionGrouping(ctx); + } + + @Override + public Node visitSessionGrouping(RelationalSqlParser.SessionGroupingContext ctx) { + return super.visitSessionGrouping(ctx); + } + + @Override + public Node visitCountGrouping(RelationalSqlParser.CountGroupingContext ctx) { + return super.visitCountGrouping(ctx); + } + + @Override + public Node visitKeepExpression(RelationalSqlParser.KeepExpressionContext ctx) { + return super.visitKeepExpression(ctx); + } + @Override public Node visitSingleGroupingSet(RelationalSqlParser.SingleGroupingSetContext ctx) { return new SimpleGroupBy( @@ -795,61 +915,6 @@ public Node visitQuotedIdentifier(RelationalSqlParser.QuotedIdentifierContext ct return new Identifier(getLocation(ctx), identifier, true); } - @Override - public Node visitTimenGrouping(RelationalSqlParser.TimenGroupingContext ctx) { - return super.visitTimenGrouping(ctx); - } - - @Override - public Node visitVariationGrouping(RelationalSqlParser.VariationGroupingContext ctx) { - return super.visitVariationGrouping(ctx); - } - - @Override - public Node visitConditionGrouping(RelationalSqlParser.ConditionGroupingContext ctx) { - return super.visitConditionGrouping(ctx); - } - - @Override - public Node visitSessionGrouping(RelationalSqlParser.SessionGroupingContext ctx) { - return super.visitSessionGrouping(ctx); - } - - @Override - public Node visitCountGrouping(RelationalSqlParser.CountGroupingContext ctx) { - return super.visitCountGrouping(ctx); - } - - @Override - public Node visitLeftClosedRightOpen(RelationalSqlParser.LeftClosedRightOpenContext ctx) { - return super.visitLeftClosedRightOpen(ctx); - } - - @Override - public Node visitLeftOpenRightClosed(RelationalSqlParser.LeftOpenRightClosedContext ctx) { - return super.visitLeftOpenRightClosed(ctx); - } - - @Override - public Node visitTimeValue(RelationalSqlParser.TimeValueContext ctx) { - return super.visitTimeValue(ctx); - } - - @Override - public Node visitDateExpression(RelationalSqlParser.DateExpressionContext ctx) { - return super.visitDateExpression(ctx); - } - - @Override - public Node visitDatetimeLiteral(RelationalSqlParser.DatetimeLiteralContext ctx) { - return super.visitDatetimeLiteral(ctx); - } - - @Override - public Node visitKeepExpression(RelationalSqlParser.KeepExpressionContext ctx) { - return super.visitKeepExpression(ctx); - } - // ***************** boolean expressions ****************** @Override public Node visitLogicalNot(RelationalSqlParser.LogicalNotContext ctx) { @@ -1403,11 +1468,6 @@ public Node visitIntervalField(RelationalSqlParser.IntervalFieldContext ctx) { return super.visitIntervalField(ctx); } - @Override - public Node visitTimeDuration(RelationalSqlParser.TimeDurationContext ctx) { - return super.visitTimeDuration(ctx); - } - // ***************** arguments ***************** @Override public Node visitGenericType(RelationalSqlParser.GenericTypeContext ctx) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/SqlParser.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/SqlParser.java index c82e1f8a56f4..b20fb8293269 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/SqlParser.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/SqlParser.java @@ -44,6 +44,7 @@ import org.antlr.v4.runtime.misc.Pair; import org.antlr.v4.runtime.tree.TerminalNode; +import java.time.ZoneId; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -93,35 +94,43 @@ public SqlParser(BiConsumer initializer this.initializer = requireNonNull(initializer, "initializer is null"); } - public Statement createStatement(String sql) { - return (Statement) invokeParser("statement", sql, RelationalSqlParser::singleStatement); + public Statement createStatement(String sql, ZoneId zoneId) { + return (Statement) invokeParser("statement", sql, RelationalSqlParser::singleStatement, zoneId); } - public Statement createStatement(String sql, NodeLocation location) { + public Statement createStatement(String sql, NodeLocation location, ZoneId zoneId) { return (Statement) invokeParser( - "statement", sql, Optional.ofNullable(location), RelationalSqlParser::singleStatement); + "statement", + sql, + Optional.ofNullable(location), + RelationalSqlParser::singleStatement, + zoneId); } - public Expression createExpression(String expression) { + public Expression createExpression(String expression, ZoneId zoneId) { return (Expression) - invokeParser("expression", expression, RelationalSqlParser::standaloneExpression); + invokeParser("expression", expression, RelationalSqlParser::standaloneExpression, zoneId); } - public DataType createType(String expression) { - return (DataType) invokeParser("type", expression, RelationalSqlParser::standaloneType); + public DataType createType(String expression, ZoneId zoneId) { + return (DataType) invokeParser("type", expression, RelationalSqlParser::standaloneType, zoneId); } private Node invokeParser( - String name, String sql, Function parseFunction) { - return invokeParser(name, sql, Optional.empty(), parseFunction); + String name, + String sql, + Function parseFunction, + ZoneId zoneId) { + return invokeParser(name, sql, Optional.empty(), parseFunction, zoneId); } private Node invokeParser( String name, String sql, Optional location, - Function parseFunction) { + Function parseFunction, + ZoneId zoneId) { try { RelationalSqlLexer lexer = new RelationalSqlLexer(new CaseInsensitiveStream(CharStreams.fromString(sql))); @@ -178,7 +187,7 @@ public Token recoverInline(Parser recognizer) throws RecognitionException { throw e; } - return new AstBuilder(location.orElse(null)).visit(tree); + return new AstBuilder(location.orElse(null), zoneId).visit(tree); } catch (StackOverflowError e) { throw new ParsingException(name + " is too large (stack overflow while parsing)"); } @@ -215,15 +224,16 @@ public void exitBackQuotedIdentifier(RelationalSqlParser.BackQuotedIdentifierCon token.getCharPositionInLine() + 1); } - @Override - public void exitDigitIdentifier(RelationalSqlParser.DigitIdentifierContext context) { - Token token = context.DIGIT_IDENTIFIER().getSymbol(); - throw new ParsingException( - "identifiers must not start with a digit; surround the identifier with double quotes", - null, - token.getLine(), - token.getCharPositionInLine() + 1); - } + // @Override + // public void exitDigitIdentifier(RelationalSqlParser.DigitIdentifierContext context) { + // Token token = context.DIGIT_IDENTIFIER().getSymbol(); + // throw new ParsingException( + // "identifiers must not start with a digit; surround the identifier with double + // quotes", + // null, + // token.getLine(), + // token.getCharPositionInLine() + 1); + // } @Override public void exitNonReserved(RelationalSqlParser.NonReservedContext context) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ReservedIdentifiers.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ReservedIdentifiers.java index d40f6880b9b2..2fde6ee0d311 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ReservedIdentifiers.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ReservedIdentifiers.java @@ -23,6 +23,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.ParsingException; import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser; +import java.time.ZoneId; import java.util.Set; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -40,7 +41,7 @@ public static Set reservedIdentifiers() { public static boolean reserved(String name) { try { - return !(PARSER.createExpression(name) instanceof Identifier); + return !(PARSER.createExpression(name, ZoneId.systemDefault()) instanceof Identifier); } catch (ParsingException ignored) { return true; } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java index 1a3c394af7de..98e0a6f09aa5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java @@ -61,12 +61,15 @@ private DateTimeUtils() { static { switch (CommonDescriptor.getInstance().getConfig().getTimestampPrecision()) { case "us": + case "microsecond": CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000; break; case "ns": + case "nanosecond": CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000000; break; case "ms": + case "millisecond": default: CAST_TIMESTAMP_TO_MS = timestamp -> timestamp; break; @@ -501,7 +504,7 @@ public static long getInstantWithPrecision(String str, String timestampPrecision try { ZonedDateTime zonedDateTime = ZonedDateTime.parse(str, formatter); Instant instant = zonedDateTime.toInstant(); - if ("us".equals(timestampPrecision)) { + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { if (instant.getEpochSecond() < 0 && instant.getNano() > 0) { // adjustment can reduce the loss of the division long millis = Math.multiplyExact(instant.getEpochSecond() + 1, 1000_000L); @@ -511,7 +514,7 @@ public static long getInstantWithPrecision(String str, String timestampPrecision long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000L); return Math.addExact(millis, instant.getNano() / 1000); } - } else if ("ns".equals(timestampPrecision)) { + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000_000L); return Math.addExact(millis, instant.getNano()); } @@ -629,9 +632,11 @@ public static long convertDurationStrToLong( long res = value; switch (durationUnit) { case y: + case year: res *= 365 * 86_400_000L; break; case mo: + case month: if (currentTime == -1) { res *= 30 * 86_400_000L; } else { @@ -643,44 +648,55 @@ public static long convertDurationStrToLong( } break; case w: + case week: res *= 7 * 86_400_000L; break; case d: + case day: res *= 86_400_000L; break; case h: + case hour: res *= 3_600_000L; break; case m: + case minute: res *= 60_000L; break; case s: + case second: res *= 1_000L; break; default: break; } - if ("us".equals(timestampPrecision)) { - if (unit.equals(DurationUnit.ns.toString())) { + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { return value / 1000; - } else if (unit.equals(DurationUnit.us.toString())) { + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { return value; } else { return res * 1000; } - } else if ("ns".equals(timestampPrecision)) { - if (unit.equals(DurationUnit.ns.toString())) { + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { return value; - } else if (unit.equals(DurationUnit.us.toString())) { + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { return value * 1000; } else { return res * 1000_000; } } else { - if (unit.equals(DurationUnit.ns.toString())) { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { return value / 1000_000; - } else if (unit.equals(DurationUnit.us.toString())) { + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { return value / 1000; } else { return res; @@ -689,9 +705,9 @@ public static long convertDurationStrToLong( } public static TimeUnit timestampPrecisionStringToTimeUnit(String timestampPrecision) { - if ("us".equals(timestampPrecision)) { + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { return TimeUnit.MICROSECONDS; - } else if ("ns".equals(timestampPrecision)) { + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { return TimeUnit.NANOSECONDS; } else { return TimeUnit.MILLISECONDS; @@ -706,9 +722,11 @@ public static String convertLongToDate(long timestamp) { public static String convertLongToDate(long timestamp, String sourcePrecision) { switch (sourcePrecision) { case "ns": + case "nanosecond": timestamp /= 1000_000; break; case "us": + case "microsecond": timestamp /= 1000; break; } @@ -726,30 +744,46 @@ public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) { public enum DurationUnit { y, + year, mo, + month, w, + week, d, + day, h, + hour, m, + minute, s, + second, ms, + millisecond, us, - ns + microsecond, + ns, + nanosecond } public static TimeUnit toTimeUnit(String t) { switch (t) { case "h": + case "hour": return TimeUnit.HOURS; case "m": + case "minute": return TimeUnit.MINUTES; case "s": + case "second": return TimeUnit.SECONDS; case "ms": + case "millisecond": return TimeUnit.MILLISECONDS; case "u": + case "microsecond": return TimeUnit.MICROSECONDS; case "n": + case "nanosecond": return TimeUnit.NANOSECONDS; default: throw new IllegalArgumentException("time precision must be one of: h,m,s,ms,u,n"); @@ -781,30 +815,33 @@ public static TimeDuration constructTimeDuration(String duration) { long temp = 0; long monthDuration = 0; long nonMonthDuration = 0; - for (int i = 0; i < duration.length(); i++) { + int i = 0; + for (; i < duration.length(); i++) { char ch = duration.charAt(i); if (Character.isDigit(ch)) { temp *= 10; temp += (ch - '0'); } else { - String unit = String.valueOf(duration.charAt(i)); - // This is to identify units with two letters. - if (i + 1 < duration.length() && !Character.isDigit(duration.charAt(i + 1))) { + StringBuilder unit = new StringBuilder(String.valueOf(duration.charAt(i))); + i++; + // This is to identify units. + while (i < duration.length() && !Character.isDigit(duration.charAt(i))) { + unit.append(duration.charAt(i)); i++; - unit += duration.charAt(i); } - if (unit.equals("y")) { + i--; + if ("y".contentEquals(unit) || "year".contentEquals(unit)) { monthDuration += temp * 12; temp = 0; continue; } - if (unit.equals("mo")) { + if ("mo".contentEquals(unit) || "month".contentEquals(unit)) { monthDuration += temp; temp = 0; continue; } nonMonthDuration += - DateTimeUtils.convertDurationStrToLong(-1, temp, unit, currTimePrecision); + DateTimeUtils.convertDurationStrToLong(-1, temp, unit.toString(), currTimePrecision); temp = 0; } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java index 036089787076..335d838587ed 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java @@ -485,7 +485,7 @@ context, metadata, sessionInfo, getFakePartitionFetcher(), WarningCollector.NOOP public static Analysis analyzeSQL(String sql, Metadata metadata) { try { SqlParser sqlParser = new SqlParser(); - Statement statement = sqlParser.createStatement(sql); + Statement statement = sqlParser.createStatement(sql, ZoneId.systemDefault()); SessionInfo session = new SessionInfo( 0, "test", ZoneId.systemDefault(), "testdb", IClientSession.SqlDialect.TABLE); diff --git a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 index 26cd32456e67..4e7d8ebff855 100644 --- a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 +++ b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4 @@ -419,7 +419,7 @@ groupBy ; groupingElement - : TIME? '(' (timeRange ',')? windowInterval=timeDuration (',' windowStep=timeDuration)?')' #timenGrouping + : TIME '(' (timeRange ',')? windowInterval=timeDuration (',' windowStep=timeDuration)?')' #timenGrouping | VARIATION '(' expression (',' delta=number)? (',' propertyAssignments)? ')' #variationGrouping | CONDITION '(' expression (',' keepExpression)? (',' propertyAssignments)? ')' #conditionGrouping | SESSION '(' timeInterval=timeDuration ')' #sessionGrouping @@ -611,7 +611,7 @@ intervalField ; timeDuration - : (INTEGER_VALUE+ (intervalField))+ + : (INTEGER_VALUE intervalField)+ ; type @@ -693,7 +693,6 @@ identifier | QUOTED_IDENTIFIER #quotedIdentifier | nonReserved #unquotedIdentifier | BACKQUOTED_IDENTIFIER #backQuotedIdentifier - | DIGIT_IDENTIFIER #digitIdentifier ; number @@ -1133,9 +1132,9 @@ IDENTIFIER : (LETTER | '_') (LETTER | DIGIT | '_')* ; -DIGIT_IDENTIFIER - : DIGIT (LETTER | DIGIT | '_')+ - ; +//DIGIT_IDENTIFIER +// : DIGIT (LETTER | DIGIT | '_')+ +// ; QUOTED_IDENTIFIER : '"' ( ~'"' | '""' )* '"' @@ -1204,4 +1203,4 @@ WS // when splitting statements with DelimiterLexer UNRECOGNIZED : . - ; + ; \ No newline at end of file