You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2024/02/27 13:27:30 UTC
(iotdb) 02/02: partial
This is an automated email from the ASF dual-hosted git repository.
jackietien pushed a commit to branch ty/TableModelGrammar
in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 29153818e070259ade89fdc62c1cd03220a39996
Author: JackieTien97 <ja...@gmail.com>
AuthorDate: Tue Feb 27 21:27:20 2024 +0800
partial
---
.../iotdb/db/queryengine/common/SessionInfo.java | 29 +
.../relational/analyzer/AggregationAnalyzer.java | 459 +++++++
.../plan/relational/analyzer/Analysis.java | 179 ++-
...essionAnalyzer.java => CorrelationSupport.java} | 5 +-
.../relational/analyzer/ExpressionAnalyzer.java | 68 +-
.../relational/analyzer/ExpressionTreeUtils.java | 15 +
.../analyzer/ScopeReferenceExtractor.java | 60 +
.../relational/analyzer/StatementAnalyzer.java | 1371 +++++++++++++++++---
.../ColumnHandle.java} | 22 +-
.../plan/relational/metadata/ColumnMetadata.java | 213 +++
.../plan/relational/metadata/ColumnSchema.java | 121 ++
.../plan/relational/metadata/Metadata.java | 58 +
.../plan/relational/metadata/MetadataUtil.java | 94 +-
.../TableHandle.java} | 4 +-
.../plan/relational/metadata/TableMetadata.java | 93 ++
.../TableSchema.java} | 24 +-
.../plan/relational/planner/Symbol.java | 77 ++
.../SymbolResolver.java} | 6 +-
.../AccessControl.java} | 4 +-
.../Identity.java} | 14 +-
.../iotdb/tsfile/read/common/type/BinaryType.java | 14 +-
.../iotdb/tsfile/read/common/type/BooleanType.java | 14 +-
.../iotdb/tsfile/read/common/type/DoubleType.java | 14 +-
.../iotdb/tsfile/read/common/type/FloatType.java | 14 +-
.../iotdb/tsfile/read/common/type/IntType.java | 14 +-
.../iotdb/tsfile/read/common/type/LongType.java | 14 +-
.../apache/iotdb/tsfile/read/common/type/Type.java | 7 +
27 files changed, 2731 insertions(+), 276 deletions(-)
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
index 43c933585a9..a4a28dd2f38 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
@@ -20,23 +20,30 @@
package org.apache.iotdb.db.queryengine.common;
import org.apache.iotdb.commons.conf.IoTDBConstant.ClientVersion;
+import org.apache.iotdb.db.queryengine.plan.relational.security.Identity;
import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+import javax.annotation.Nullable;
+
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Optional;
public class SessionInfo {
private final long sessionId;
private final String userName;
private final String zoneId;
+ @Nullable private final String databaseName;
+
private ClientVersion version = ClientVersion.V_1_0;
public SessionInfo(long sessionId, String userName, String zoneId) {
this.sessionId = sessionId;
this.userName = userName;
this.zoneId = zoneId;
+ this.databaseName = null;
}
public SessionInfo(long sessionId, String userName, String zoneId, ClientVersion version) {
@@ -44,6 +51,20 @@ public class SessionInfo {
this.userName = userName;
this.zoneId = zoneId;
this.version = version;
+ this.databaseName = null;
+ }
+
+ public SessionInfo(
+ long sessionId,
+ String userName,
+ String zoneId,
+ @Nullable String databaseName,
+ ClientVersion version) {
+ this.sessionId = sessionId;
+ this.userName = userName;
+ this.zoneId = zoneId;
+ this.databaseName = databaseName;
+ this.version = version;
}
public long getSessionId() {
@@ -62,6 +83,14 @@ public class SessionInfo {
return version;
}
+ public Identity getIdentity() {
+ return new Identity(userName);
+ }
+
+ public Optional<String> getDatabaseName() {
+ return Optional.ofNullable(databaseName);
+ }
+
public static SessionInfo deserializeFrom(ByteBuffer buffer) {
long sessionId = ReadWriteIOUtils.readLong(buffer);
String userName = ReadWriteIOUtils.readString(buffer);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
new file mode 100644
index 00000000000..b1a642d95e5
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
@@ -0,0 +1,459 @@
+/*
+ * 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.analyzer;
+
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticUnaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.CurrentTime;
+import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ExistsPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FieldReference;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+import org.apache.iotdb.db.relational.sql.tree.IfExpression;
+import org.apache.iotdb.db.relational.sql.tree.InListExpression;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNotNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LikePredicate;
+import org.apache.iotdb.db.relational.sql.tree.Literal;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
+import org.apache.iotdb.db.relational.sql.tree.Trim;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
+
+import com.google.common.collect.ImmutableList;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.isAggregationFunction;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.getReferencesToScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.hasReferencesToScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.isFieldFromScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware.scopeAwareKey;
+
+/** Checks whether an expression is constant with respect to the group */
+class AggregationAnalyzer {
+
+ // fields and expressions in the group by clause
+ private final Set<FieldId> groupingFields;
+ private final Set<ScopeAware<Expression>> expressions;
+ private final Map<NodeRef<Expression>, ResolvedField> columnReferences;
+
+ private final Analysis analysis;
+ private final Scope sourceScope;
+ private final Optional<Scope> orderByScope;
+
+ public static void verifySourceAggregations(
+ List<Expression> groupByExpressions,
+ Scope sourceScope,
+ List<Expression> expressions,
+ Analysis analysis) {
+ AggregationAnalyzer analyzer =
+ new AggregationAnalyzer(groupByExpressions, sourceScope, Optional.empty(), analysis);
+ for (Expression expression : expressions) {
+ analyzer.analyze(expression);
+ }
+ }
+
+ public static void verifyOrderByAggregations(
+ List<Expression> groupByExpressions,
+ Scope sourceScope,
+ Scope orderByScope,
+ List<Expression> expressions,
+ Analysis analysis) {
+ AggregationAnalyzer analyzer =
+ new AggregationAnalyzer(
+ groupByExpressions, sourceScope, Optional.of(orderByScope), analysis);
+ for (Expression expression : expressions) {
+ analyzer.analyze(expression);
+ }
+ }
+
+ private AggregationAnalyzer(
+ List<Expression> groupByExpressions,
+ Scope sourceScope,
+ Optional<Scope> orderByScope,
+ Analysis analysis) {
+ requireNonNull(groupByExpressions, "groupByExpressions is null");
+ requireNonNull(sourceScope, "sourceScope is null");
+ requireNonNull(orderByScope, "orderByScope is null");
+ requireNonNull(analysis, "analysis is null");
+
+ this.sourceScope = sourceScope;
+ this.orderByScope = orderByScope;
+ this.analysis = analysis;
+ this.expressions =
+ groupByExpressions.stream()
+ .map(expression -> scopeAwareKey(expression, analysis, sourceScope))
+ .collect(toImmutableSet());
+
+ // No defensive copy here for performance reasons.
+ // Copying this map may lead to quadratic time complexity
+ this.columnReferences = analysis.getColumnReferenceFields();
+
+ this.groupingFields =
+ groupByExpressions.stream()
+ .map(NodeRef::of)
+ .filter(columnReferences::containsKey)
+ .map(columnReferences::get)
+ .map(ResolvedField::getFieldId)
+ .collect(toImmutableSet());
+
+ this.groupingFields.forEach(
+ fieldId -> {
+ checkState(
+ isFieldFromScope(fieldId, sourceScope),
+ "Grouping field %s should originate from %s",
+ fieldId,
+ sourceScope.getRelationType());
+ });
+ }
+
+ private void analyze(Expression expression) {
+ Visitor visitor = new Visitor();
+ if (!visitor.process(expression, null)) {
+ throw new SemanticException(
+ String.format(
+ "'%s' must be an aggregate expression or appear in GROUP BY clause", expression));
+ }
+ }
+
+ /** visitor returns true if all expressions are constant with respect to the group. */
+ private class Visitor extends AstVisitor<Boolean, Void> {
+ @Override
+ protected Boolean visitExpression(Expression node, Void context) {
+ throw new UnsupportedOperationException(
+ "aggregation analysis not yet implemented for: " + node.getClass().getName());
+ }
+
+ @Override
+ protected Boolean visitSubqueryExpression(SubqueryExpression node, Void context) {
+ /*
+ * Column reference can resolve to (a) some subquery's scope, (b) a projection (ORDER BY scope),
+ * (c) source scope or (d) outer query scope (effectively a constant).
+ * From AggregationAnalyzer's perspective, only case (c) needs verification.
+ */
+ getReferencesToScope(node, analysis, sourceScope)
+ .filter(expression -> !isGroupingKey(expression))
+ .findFirst()
+ .ifPresent(
+ expression -> {
+ throw new SemanticException(
+ String.format(
+ "Subquery uses '%s' which must appear in GROUP BY clause", expression));
+ });
+
+ return true;
+ }
+
+ @Override
+ protected Boolean visitExists(ExistsPredicate node, Void context) {
+ checkState(node.getSubquery() instanceof SubqueryExpression);
+ return process(node.getSubquery(), context);
+ }
+
+ @Override
+ protected Boolean visitCast(Cast node, Void context) {
+ return process(node.getExpression(), context);
+ }
+
+ @Override
+ protected Boolean visitCoalesceExpression(CoalesceExpression node, Void context) {
+ return node.getOperands().stream().allMatch(expression -> process(expression, context));
+ }
+
+ @Override
+ protected Boolean visitNullIfExpression(NullIfExpression node, Void context) {
+ return process(node.getFirst(), context) && process(node.getSecond(), context);
+ }
+
+ @Override
+ protected Boolean visitBetweenPredicate(BetweenPredicate node, Void context) {
+ return process(node.getMin(), context)
+ && process(node.getValue(), context)
+ && process(node.getMax(), context);
+ }
+
+ @Override
+ protected Boolean visitCurrentTime(CurrentTime node, Void context) {
+ return true;
+ }
+
+ @Override
+ protected Boolean visitArithmeticBinary(ArithmeticBinaryExpression node, Void context) {
+ return process(node.getLeft(), context) && process(node.getRight(), context);
+ }
+
+ @Override
+ protected Boolean visitComparisonExpression(ComparisonExpression node, Void context) {
+ return process(node.getLeft(), context) && process(node.getRight(), context);
+ }
+
+ @Override
+ protected Boolean visitLiteral(Literal node, Void context) {
+ return true;
+ }
+
+ @Override
+ protected Boolean visitIsNotNullPredicate(IsNotNullPredicate node, Void context) {
+ return process(node.getValue(), context);
+ }
+
+ @Override
+ protected Boolean visitIsNullPredicate(IsNullPredicate node, Void context) {
+ return process(node.getValue(), context);
+ }
+
+ @Override
+ protected Boolean visitLikePredicate(LikePredicate node, Void context) {
+ return process(node.getValue(), context) && process(node.getPattern(), context);
+ }
+
+ @Override
+ protected Boolean visitInListExpression(InListExpression node, Void context) {
+ return node.getValues().stream().allMatch(expression -> process(expression, context));
+ }
+
+ @Override
+ protected Boolean visitInPredicate(InPredicate node, Void context) {
+ return process(node.getValue(), context) && process(node.getValueList(), context);
+ }
+
+ @Override
+ protected Boolean visitQuantifiedComparisonExpression(
+ QuantifiedComparisonExpression node, Void context) {
+ return process(node.getValue(), context) && process(node.getSubquery(), context);
+ }
+
+ @Override
+ protected Boolean visitTrim(Trim node, Void context) {
+ return process(node.getTrimSource(), context)
+ && (!node.getTrimCharacter().isPresent()
+ || process(node.getTrimCharacter().get(), context));
+ }
+
+ @Override
+ protected Boolean visitFunctionCall(FunctionCall node, Void context) {
+ if (isAggregationFunction(node.getName().toString())) {
+ List<FunctionCall> aggregateFunctions = extractAggregateFunctions(node.getArguments());
+
+ if (!aggregateFunctions.isEmpty()) {
+ throw new SemanticException(
+ String.format(
+ "Cannot nest aggregations inside aggregation '%s': %s",
+ node.getName(), aggregateFunctions));
+ }
+
+ return true;
+ }
+
+ return node.getArguments().stream().allMatch(expression -> process(expression, context));
+ }
+
+ @Override
+ protected Boolean visitIdentifier(Identifier node, Void context) {
+ // if (analysis.getLambdaArgumentReferences().containsKey(NodeRef.of(node))) {
+ // return true;
+ // }
+
+ if (!hasReferencesToScope(node, analysis, sourceScope)) {
+ // reference to outer scope is group-invariant
+ return true;
+ }
+
+ return isGroupingKey(node);
+ }
+
+ @Override
+ protected Boolean visitDereferenceExpression(DereferenceExpression node, Void context) {
+
+ if (!hasReferencesToScope(node, analysis, sourceScope)) {
+ // reference to outer scope is group-invariant
+ return true;
+ }
+
+ if (columnReferences.containsKey(NodeRef.<Expression>of(node))) {
+ return isGroupingKey(node);
+ }
+
+ // Allow SELECT col1.f1 FROM table1 GROUP BY col1
+ return process(node.getBase(), context);
+ }
+
+ private boolean isGroupingKey(Expression node) {
+ FieldId fieldId =
+ requireNonNull(columnReferences.get(NodeRef.of(node)), () -> "No field for " + node)
+ .getFieldId();
+
+ if (orderByScope.isPresent() && isFieldFromScope(fieldId, orderByScope.get())) {
+ return true;
+ }
+
+ return groupingFields.contains(fieldId);
+ }
+
+ @Override
+ protected Boolean visitFieldReference(FieldReference node, Void context) {
+ if (orderByScope.isPresent()) {
+ return true;
+ }
+
+ FieldId fieldId =
+ requireNonNull(columnReferences.get(NodeRef.of(node)), () -> "No field for " + node)
+ .getFieldId();
+ boolean inGroup = groupingFields.contains(fieldId);
+ if (!inGroup) {
+ Field field = sourceScope.getRelationType().getFieldByIndex(node.getFieldIndex());
+
+ String column;
+ if (!field.getName().isPresent()) {
+ column = Integer.toString(node.getFieldIndex() + 1);
+ } else if (field.getRelationAlias().isPresent()) {
+ column = format("'%s.%s'", field.getRelationAlias().get(), field.getName().get());
+ } else {
+ column = "'" + field.getName().get() + "'";
+ }
+
+ throw new SemanticException(String.format("Column %s not in GROUP BY clause", column));
+ }
+ return inGroup;
+ }
+
+ @Override
+ protected Boolean visitArithmeticUnary(ArithmeticUnaryExpression node, Void context) {
+ return process(node.getValue(), context);
+ }
+
+ @Override
+ protected Boolean visitNotExpression(NotExpression node, Void context) {
+ return process(node.getValue(), context);
+ }
+
+ @Override
+ protected Boolean visitLogicalExpression(LogicalExpression node, Void context) {
+ return node.getTerms().stream().allMatch(item -> process(item, context));
+ }
+
+ @Override
+ protected Boolean visitIfExpression(IfExpression node, Void context) {
+ ImmutableList.Builder<Expression> expressions =
+ ImmutableList.<Expression>builder().add(node.getCondition()).add(node.getTrueValue());
+
+ if (node.getFalseValue().isPresent()) {
+ expressions.add(node.getFalseValue().get());
+ }
+
+ return expressions.build().stream().allMatch(expression -> process(expression, context));
+ }
+
+ @Override
+ protected Boolean visitSimpleCaseExpression(SimpleCaseExpression node, Void context) {
+ if (!process(node.getOperand(), context)) {
+ return false;
+ }
+
+ for (WhenClause whenClause : node.getWhenClauses()) {
+ if (!process(whenClause.getOperand(), context)
+ || !process(whenClause.getResult(), context)) {
+ return false;
+ }
+ }
+
+ return !node.getDefaultValue().isPresent() || process(node.getDefaultValue().get(), context);
+ }
+
+ @Override
+ protected Boolean visitSearchedCaseExpression(SearchedCaseExpression node, Void context) {
+ for (WhenClause whenClause : node.getWhenClauses()) {
+ if (!process(whenClause.getOperand(), context)
+ || !process(whenClause.getResult(), context)) {
+ return false;
+ }
+ }
+
+ return !node.getDefaultValue().isPresent() || process(node.getDefaultValue().get(), context);
+ }
+
+ @Override
+ protected Boolean visitParameter(Parameter node, Void context) {
+ // if (analysis.isDescribe()) {
+ // return true;
+ // }
+ Map<NodeRef<Parameter>, Expression> parameters = analysis.getParameters();
+ checkArgument(
+ node.getId() < parameters.size(),
+ "Invalid parameter number %s, max values is %s",
+ node.getId(),
+ parameters.size() - 1);
+ return process(parameters.get(NodeRef.of(node)), context);
+ }
+
+ @Override
+ public Boolean process(Node node, @Nullable Void context) {
+ if (node instanceof Expression
+ && expressions.contains(scopeAwareKey(node, analysis, sourceScope))
+ && (!orderByScope.isPresent() || !hasOrderByReferencesToOutputColumns(node))) {
+ return true;
+ }
+
+ return super.process(node, context);
+ }
+
+ private boolean hasOrderByReferencesToOutputColumns(Node node) {
+ return hasReferencesToScope(node, analysis, orderByScope.get());
+ }
+
+ private void verifyNoOrderByReferencesToOutputColumns(Node node, String errorString) {
+ getReferencesToScope(node, analysis, orderByScope.get())
+ .findFirst()
+ .ifPresent(
+ expression -> {
+ throw new SemanticException(errorString);
+ });
+ }
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
index ad2e99dc599..0a2d4ff03df 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
@@ -19,7 +19,11 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.queryengine.plan.relational.security.Identity;
import org.apache.iotdb.db.relational.sql.tree.AllColumns;
import org.apache.iotdb.db.relational.sql.tree.ExistsPredicate;
import org.apache.iotdb.db.relational.sql.tree.Expression;
@@ -30,6 +34,7 @@ import org.apache.iotdb.db.relational.sql.tree.Node;
import org.apache.iotdb.db.relational.sql.tree.Offset;
import org.apache.iotdb.db.relational.sql.tree.OrderBy;
import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
import org.apache.iotdb.db.relational.sql.tree.Query;
import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
@@ -88,6 +93,17 @@ public class Analysis {
// map inner recursive reference in the expandable query to the recursion base scope
private final Map<NodeRef<Node>, Scope> expandableBaseScopes = new LinkedHashMap<>();
+ // Synthetic scope when a query does not have a FROM clause
+ // We need to track this separately because there's no node we can attach it to.
+ private final Map<NodeRef<QuerySpecification>, Scope> implicitFromScopes = new LinkedHashMap<>();
+ private final Map<NodeRef<Node>, Scope> scopes = new LinkedHashMap<>();
+
+ private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>();
+
+ // a map of users to the columns per table that they access
+ private final Map<AccessControlInfo, Map<QualifiedObjectName, Set<String>>>
+ tableColumnReferences = new LinkedHashMap<>();
+
private final Map<NodeRef<Offset>, Long> offset = new LinkedHashMap<>();
private final Map<NodeRef<Node>, OptionalLong> limit = new LinkedHashMap<>();
private final Map<NodeRef<AllColumns>, List<Field>> selectAllResultFields = new LinkedHashMap<>();
@@ -96,6 +112,8 @@ public class Analysis {
private final Map<NodeRef<Join>, JoinUsingAnalysis> joinUsing = new LinkedHashMap<>();
private final Map<NodeRef<Node>, SubqueryAnalysis> subQueries = new LinkedHashMap<>();
+ private final Map<NodeRef<Table>, TableEntry> tables = new LinkedHashMap<>();
+
private final Map<NodeRef<Expression>, Type> types = new LinkedHashMap<>();
private final Map<NodeRef<Expression>, Type> coercions = new LinkedHashMap<>();
@@ -103,9 +121,7 @@ public class Analysis {
private final Map<NodeRef<Relation>, List<Type>> relationCoercions = new LinkedHashMap<>();
- private final Map<NodeRef<Node>, Scope> scopes = new LinkedHashMap<>();
-
- private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>();
+ private final Map<Field, ColumnHandle> columns = new LinkedHashMap<>();
private final Map<NodeRef<QuerySpecification>, List<FunctionCall>> aggregates =
new LinkedHashMap<>();
@@ -122,6 +138,12 @@ public class Analysis {
private final Multimap<Field, SourceColumn> originColumnDetails = ArrayListMultimap.create();
+ private final Multimap<NodeRef<Expression>, Field> fieldLineage = ArrayListMultimap.create();
+
+ private final Map<NodeRef<Relation>, QualifiedName> relationNames = new LinkedHashMap<>();
+
+ private final Set<NodeRef<Relation>> aliasedRelations = new LinkedHashSet<>();
+
public Analysis(@Nullable Statement root, Map<NodeRef<Parameter>, Expression> parameters) {
this.root = root;
this.parameters = ImmutableMap.copyOf(requireNonNull(parameters, "parameters is null"));
@@ -186,6 +208,14 @@ public class Analysis {
format("Analysis does not contain information for node: %s", node)));
}
+ public void setImplicitFromScope(QuerySpecification node, Scope scope) {
+ implicitFromScopes.put(NodeRef.of(node), scope);
+ }
+
+ public Scope getImplicitFromScope(QuerySpecification node) {
+ return implicitFromScopes.get(NodeRef.of(node));
+ }
+
public Optional<Scope> tryGetScope(Node node) {
NodeRef<Node> key = NodeRef.of(node);
if (scopes.containsKey(key)) {
@@ -257,6 +287,14 @@ public class Analysis {
relationCoercions.put(NodeRef.of(relation), ImmutableList.copyOf(types));
}
+ public void setColumn(Field field, ColumnHandle handle) {
+ columns.put(field, handle);
+ }
+
+ public ColumnHandle getColumn(Field field) {
+ return columns.get(field);
+ }
+
public Map<NodeRef<Expression>, Type> getCoercions() {
return unmodifiableMap(coercions);
}
@@ -278,6 +316,14 @@ public class Analysis {
this.typeOnlyCoercions.addAll(typeOnlyCoercions);
}
+ public Set<NodeRef<Expression>> getTypeOnlyCoercions() {
+ return unmodifiableSet(typeOnlyCoercions);
+ }
+
+ public boolean isTypeOnlyCoercion(Expression expression) {
+ return typeOnlyCoercions.contains(NodeRef.of(expression));
+ }
+
public void setGroupingSets(QuerySpecification node, GroupingSetAnalysis groupingSets) {
this.groupingSets.put(NodeRef.of(node), groupingSets);
}
@@ -393,6 +439,26 @@ public class Analysis {
return subQueries.computeIfAbsent(NodeRef.of(node), key -> new SubqueryAnalysis());
}
+ public TableHandle getTableHandle(Table table) {
+ return tables
+ .get(NodeRef.of(table))
+ .getHandle()
+ .orElseThrow(
+ () -> new IllegalArgumentException(format("%s is not a table reference", table)));
+ }
+
+ public Collection<TableHandle> getTables() {
+ return tables.values().stream()
+ .map(TableEntry::getHandle)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableList());
+ }
+
+ public void registerTable(Table table, Optional<TableHandle> handle, QualifiedObjectName name) {
+ tables.put(NodeRef.of(table), new TableEntry(handle, name));
+ }
+
public ResolvedField getResolvedField(Expression expression) {
checkArgument(
isColumnReference(expression), "Expression is not a column reference: %s", expression);
@@ -404,6 +470,31 @@ public class Analysis {
return columnReferences.containsKey(NodeRef.of(expression));
}
+ public void addTableColumnReferences(
+ AccessControl accessControl,
+ Identity identity,
+ Multimap<QualifiedObjectName, String> tableColumnMap) {
+ AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity);
+ Map<QualifiedObjectName, Set<String>> references =
+ tableColumnReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>());
+ tableColumnMap
+ .asMap()
+ .forEach(
+ (key, value) -> references.computeIfAbsent(key, k -> new HashSet<>()).addAll(value));
+ }
+
+ public void addEmptyColumnReferencesForTable(
+ AccessControl accessControl, Identity identity, QualifiedObjectName table) {
+ AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity);
+ tableColumnReferences
+ .computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>())
+ .computeIfAbsent(table, k -> new HashSet<>());
+ }
+
+ public Map<AccessControlInfo, Map<QualifiedObjectName, Set<String>>> getTableColumnReferences() {
+ return tableColumnReferences;
+ }
+
public RelationType getOutputDescriptor() {
return getOutputDescriptor(root);
}
@@ -420,6 +511,88 @@ public class Analysis {
return ImmutableSet.copyOf(originColumnDetails.get(field));
}
+ public void addExpressionFields(Expression expression, Collection<Field> fields) {
+ fieldLineage.putAll(NodeRef.of(expression), fields);
+ }
+
+ public Set<SourceColumn> getExpressionSourceColumns(Expression expression) {
+ return fieldLineage.get(NodeRef.of(expression)).stream()
+ .flatMap(field -> getSourceColumns(field).stream())
+ .collect(toImmutableSet());
+ }
+
+ public void setRelationName(Relation relation, QualifiedName name) {
+ relationNames.put(NodeRef.of(relation), name);
+ }
+
+ public QualifiedName getRelationName(Relation relation) {
+ return relationNames.get(NodeRef.of(relation));
+ }
+
+ public void addAliased(Relation relation) {
+ aliasedRelations.add(NodeRef.of(relation));
+ }
+
+ public boolean isAliased(Relation relation) {
+ return aliasedRelations.contains(NodeRef.of(relation));
+ }
+
+ public static final class AccessControlInfo {
+ private final AccessControl accessControl;
+ private final Identity identity;
+
+ public AccessControlInfo(AccessControl accessControl, Identity identity) {
+ this.accessControl = requireNonNull(accessControl, "accessControl is null");
+ this.identity = requireNonNull(identity, "identity is null");
+ }
+
+ public AccessControl getAccessControl() {
+ return accessControl;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AccessControlInfo that = (AccessControlInfo) o;
+ return Objects.equals(accessControl, that.accessControl)
+ && Objects.equals(identity, that.identity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(accessControl, identity);
+ }
+
+ @Override
+ public String toString() {
+ return format("AccessControl: %s, Identity: %s", accessControl.getClass(), identity);
+ }
+ }
+
+ private static class TableEntry {
+ private final Optional<TableHandle> handle;
+ private final QualifiedObjectName name;
+
+ public TableEntry(Optional<TableHandle> handle, QualifiedObjectName name) {
+ this.handle = requireNonNull(handle, "handle is null");
+ this.name = requireNonNull(name, "name is null");
+ }
+
+ public Optional<TableHandle> getHandle() {
+ return handle;
+ }
+
+ public QualifiedObjectName getName() {
+ return name;
+ }
+ }
+
public static class SourceColumn {
private final QualifiedObjectName tableName;
private final String columnName;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
similarity index 93%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
index 61d95d2e832..dbf72adcd18 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
@@ -19,4 +19,7 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
-public class ExpressionAnalyzer {}
+public enum CorrelationSupport {
+ ALLOWED,
+ DISALLOWED
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
index 61d95d2e832..bd906b816f1 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
@@ -19,4 +19,70 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
-public class ExpressionAnalyzer {}
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+
+public class ExpressionAnalyzer {
+
+ public static ExpressionAnalysis analyzeExpression(
+ SessionInfo session,
+ AccessControl accessControl,
+ Scope scope,
+ Analysis analysis,
+ Expression expression,
+ WarningCollector warningCollector,
+ CorrelationSupport correlationSupport) {
+ // ExpressionAnalyzer analyzer =
+ // new ExpressionAnalyzer(accessControl, analysis, session,
+ // TypeProvider.empty(), warningCollector);
+ // analyzer.analyze(expression, scope, correlationSupport);
+ //
+ // updateAnalysis(analysis, analyzer, session, accessControl);
+ // analysis.addExpressionFields(expression, analyzer.getSourceFields());
+ //
+ // return new ExpressionAnalysis(
+ // analyzer.getExpressionTypes(),
+ // analyzer.getExpressionCoercions(),
+ // analyzer.getSubqueryInPredicates(),
+ // analyzer.getSubqueries(),
+ // analyzer.getExistsSubqueries(),
+ // analyzer.getColumnReferences(),
+ // analyzer.getTypeOnlyCoercions(),
+ // analyzer.getQuantifiedComparisons(),
+ // analyzer.getWindowFunctions());
+ return null;
+ }
+
+ public static class LabelPrefixedReference {
+ private final String label;
+ private final Optional<Identifier> column;
+
+ public LabelPrefixedReference(String label, Identifier column) {
+ this(label, Optional.of(requireNonNull(column, "column is null")));
+ }
+
+ public LabelPrefixedReference(String label) {
+ this(label, Optional.empty());
+ }
+
+ private LabelPrefixedReference(String label, Optional<Identifier> column) {
+ this.label = requireNonNull(label, "label is null");
+ this.column = requireNonNull(column, "column is null");
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public Optional<Identifier> getColumn() {
+ return column;
+ }
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
index 1c564af206d..8bd81cc63d8 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
@@ -19,9 +19,11 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+import org.apache.iotdb.commons.udf.builtin.BuiltinAggregationFunction;
import org.apache.iotdb.db.relational.sql.tree.DefaultExpressionTraversalVisitor;
import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
import org.apache.iotdb.db.relational.sql.tree.Identifier;
import org.apache.iotdb.db.relational.sql.tree.Node;
import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
@@ -39,6 +41,10 @@ import static java.util.Objects.requireNonNull;
public final class ExpressionTreeUtils {
private ExpressionTreeUtils() {}
+ static List<FunctionCall> extractAggregateFunctions(Iterable<? extends Node> nodes) {
+ return extractExpressions(nodes, FunctionCall.class, ExpressionTreeUtils::isAggregation);
+ }
+
public static <T extends Expression> List<T> extractExpressions(
Iterable<? extends Node> nodes, Class<T> clazz) {
return extractExpressions(nodes, clazz, alwaysTrue());
@@ -58,6 +64,10 @@ public final class ExpressionTreeUtils {
.collect(toImmutableList());
}
+ private static boolean isAggregation(FunctionCall functionCall) {
+ return isAggregationFunction(functionCall.getName().toString());
+ }
+
private static List<Node> linearizeNodes(Node node) {
ImmutableList.Builder<Node> nodes = ImmutableList.builder();
new DefaultExpressionTraversalVisitor<Void>() {
@@ -80,4 +90,9 @@ public final class ExpressionTreeUtils {
}
return name;
}
+
+ static boolean isAggregationFunction(String functionName) {
+ // TODO consider UDAF
+ return BuiltinAggregationFunction.getNativeFunctionNames().contains(functionName.toLowerCase());
+ }
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java
new file mode 100644
index 00000000000..1e4952762e0
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.analyzer;
+
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.relational.sql.util.AstUtil.preOrder;
+
+/** Extract expressions that are references to a given scope. */
+final class ScopeReferenceExtractor {
+ private ScopeReferenceExtractor() {}
+
+ public static boolean hasReferencesToScope(Node node, Analysis analysis, Scope scope) {
+ return getReferencesToScope(node, analysis, scope).findAny().isPresent();
+ }
+
+ public static Stream<Expression> getReferencesToScope(Node node, Analysis analysis, Scope scope) {
+ Map<NodeRef<Expression>, ResolvedField> columnReferences = analysis.getColumnReferenceFields();
+
+ return preOrder(node)
+ .filter(Expression.class::isInstance)
+ .map(Expression.class::cast)
+ .filter(expression -> columnReferences.containsKey(NodeRef.of(expression)))
+ .filter(expression -> isReferenceToScope(expression, scope, columnReferences));
+ }
+
+ private static boolean isReferenceToScope(
+ Expression node, Scope scope, Map<NodeRef<Expression>, ResolvedField> columnReferences) {
+ ResolvedField field = columnReferences.get(NodeRef.of(node));
+ requireNonNull(field, () -> "No Field for " + node);
+ return isFieldFromScope(field.getFieldId(), scope);
+ }
+
+ public static boolean isFieldFromScope(FieldId field, Scope scope) {
+ return Objects.equals(field.getRelationId(), scope.getRelationId());
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index f24c0ea0388..26a4d9b8c8c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -20,10 +20,19 @@
package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
import org.apache.iotdb.db.queryengine.execution.warnings.IoTDBWarning;
import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnSchema;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableSchema;
import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
import org.apache.iotdb.db.relational.sql.tree.AddColumn;
+import org.apache.iotdb.db.relational.sql.tree.AliasedRelation;
import org.apache.iotdb.db.relational.sql.tree.AllColumns;
import org.apache.iotdb.db.relational.sql.tree.AllRows;
import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
@@ -43,15 +52,23 @@ import org.apache.iotdb.db.relational.sql.tree.Explain;
import org.apache.iotdb.db.relational.sql.tree.ExplainAnalyze;
import org.apache.iotdb.db.relational.sql.tree.Expression;
import org.apache.iotdb.db.relational.sql.tree.FieldReference;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.GroupBy;
+import org.apache.iotdb.db.relational.sql.tree.GroupingElement;
+import org.apache.iotdb.db.relational.sql.tree.GroupingSets;
import org.apache.iotdb.db.relational.sql.tree.Identifier;
import org.apache.iotdb.db.relational.sql.tree.Insert;
import org.apache.iotdb.db.relational.sql.tree.Intersect;
import org.apache.iotdb.db.relational.sql.tree.Join;
+import org.apache.iotdb.db.relational.sql.tree.JoinCriteria;
+import org.apache.iotdb.db.relational.sql.tree.JoinOn;
+import org.apache.iotdb.db.relational.sql.tree.JoinUsing;
import org.apache.iotdb.db.relational.sql.tree.Limit;
import org.apache.iotdb.db.relational.sql.tree.LongLiteral;
+import org.apache.iotdb.db.relational.sql.tree.NaturalJoin;
import org.apache.iotdb.db.relational.sql.tree.Node;
import org.apache.iotdb.db.relational.sql.tree.Offset;
-import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
import org.apache.iotdb.db.relational.sql.tree.Property;
import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
import org.apache.iotdb.db.relational.sql.tree.Query;
@@ -61,14 +78,17 @@ import org.apache.iotdb.db.relational.sql.tree.RenameColumn;
import org.apache.iotdb.db.relational.sql.tree.RenameTable;
import org.apache.iotdb.db.relational.sql.tree.Select;
import org.apache.iotdb.db.relational.sql.tree.SelectItem;
+import org.apache.iotdb.db.relational.sql.tree.SetOperation;
import org.apache.iotdb.db.relational.sql.tree.SetProperties;
import org.apache.iotdb.db.relational.sql.tree.ShowDB;
import org.apache.iotdb.db.relational.sql.tree.ShowFunctions;
import org.apache.iotdb.db.relational.sql.tree.ShowIndex;
import org.apache.iotdb.db.relational.sql.tree.ShowTables;
+import org.apache.iotdb.db.relational.sql.tree.SimpleGroupBy;
import org.apache.iotdb.db.relational.sql.tree.SingleColumn;
import org.apache.iotdb.db.relational.sql.tree.SortItem;
import org.apache.iotdb.db.relational.sql.tree.Statement;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
import org.apache.iotdb.db.relational.sql.tree.Table;
import org.apache.iotdb.db.relational.sql.tree.TableSubquery;
import org.apache.iotdb.db.relational.sql.tree.Union;
@@ -78,14 +98,21 @@ import org.apache.iotdb.db.relational.sql.tree.With;
import org.apache.iotdb.db.relational.sql.tree.WithQuery;
import org.apache.iotdb.tsfile.read.common.type.Type;
-import com.google.common.base.VerifyException;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Streams;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.OptionalLong;
@@ -94,31 +121,59 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.Iterables.getLast;
import static java.lang.Math.toIntExact;
import static java.util.Collections.emptyList;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;
import static org.apache.iotdb.db.queryengine.execution.warnings.StandardWarningCode.REDUNDANT_ORDER_BY;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.AggregationAnalyzer.verifyOrderByAggregations;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.AggregationAnalyzer.verifySourceAggregations;
import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.CanonicalizationAware.canonicalizationAwareKey;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.asQualifiedName;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.Scope.BasisType.TABLE;
+import static org.apache.iotdb.db.queryengine.plan.relational.metadata.MetadataUtil.createQualifiedObjectName;
import static org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy;
import static org.apache.iotdb.db.relational.sql.tree.Join.Type.FULL;
+import static org.apache.iotdb.db.relational.sql.tree.Join.Type.INNER;
import static org.apache.iotdb.db.relational.sql.tree.Join.Type.LEFT;
import static org.apache.iotdb.db.relational.sql.tree.Join.Type.RIGHT;
import static org.apache.iotdb.db.relational.sql.util.AstUtil.preOrder;
+import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN;
public class StatementAnalyzer {
private final Analysis analysis;
+ private final AccessControl accessControl;
+
private final WarningCollector warningCollector;
- public StatementAnalyzer(Analysis analysis, WarningCollector warningCollector) {
+ private final SessionInfo sessionContext;
+
+ private final Metadata metadata;
+
+ private final CorrelationSupport correlationSupport;
+
+ public StatementAnalyzer(
+ Analysis analysis,
+ AccessControl accessControl,
+ WarningCollector warningCollector,
+ SessionInfo sessionContext,
+ Metadata metadata,
+ CorrelationSupport correlationSupport) {
this.analysis = analysis;
+ this.accessControl = accessControl;
this.warningCollector = warningCollector;
+ this.sessionContext = sessionContext;
+ this.metadata = metadata;
+ this.correlationSupport = correlationSupport;
}
public Scope analyze(Node node) {
@@ -551,7 +606,7 @@ public class StatementAnalyzer {
stepType.getVisibleFields().stream().map(Field::getType).collect(toImmutableList());
for (int i = 0; i < anchorFieldTypes.size(); i++) {
- if (!typeCoercion.canCoerce(stepFieldTypes.get(i), anchorFieldTypes.get(i))) {
+ if (stepFieldTypes.get(i) != anchorFieldTypes.get(i)) {
// TODO for more precise error location, pass the mismatching select expression instead of
// `step`
throw new SemanticException(
@@ -574,9 +629,17 @@ public class StatementAnalyzer {
@Override
protected Scope visitTableSubquery(TableSubquery node, Optional<Scope> scope) {
StatementAnalyzer analyzer =
- statementAnalyzerFactory.createStatementAnalyzer(
- analysis, session, warningCollector, CorrelationSupport.ALLOWED);
- Scope queryScope = analyzer.analyze(node.getQuery(), scope.orElseThrow());
+ new StatementAnalyzer(
+ analysis,
+ accessControl,
+ warningCollector,
+ sessionContext,
+ metadata,
+ CorrelationSupport.ALLOWED);
+ Scope queryScope =
+ analyzer.analyze(
+ node.getQuery(),
+ scope.orElseThrow(() -> new NoSuchElementException("No value present")));
return createAndAssignScope(node, scope, queryScope.getRelationType());
}
@@ -587,13 +650,11 @@ public class StatementAnalyzer {
Scope sourceScope = analyzeFrom(node, scope);
- analyzeWindowDefinitions(node, sourceScope);
- resolveFunctionCallAndMeasureWindows(node);
-
node.getWhere().ifPresent(where -> analyzeWhere(node, sourceScope, where));
List<Expression> outputExpressions = analyzeSelect(node, sourceScope);
- GroupingSetAnalysis groupByAnalysis = analyzeGroupBy(node, sourceScope, outputExpressions);
+ Analysis.GroupingSetAnalysis groupByAnalysis =
+ analyzeGroupBy(node, sourceScope, outputExpressions);
analyzeHaving(node, sourceScope);
Scope outputScope = computeAndAssignOutputScope(node, scope, sourceScope);
@@ -607,12 +668,12 @@ public class StatementAnalyzer {
orderByExpressions = analyzeOrderBy(node, orderBy.getSortItems(), orderByScope.get());
if ((sourceScope.getOuterQueryParent().isPresent() || !isTopLevel)
- && node.getLimit().isEmpty()
- && node.getOffset().isEmpty()) {
+ && !node.getLimit().isPresent()
+ && !node.getOffset().isPresent()) {
// not the root scope and ORDER BY is ineffective
analysis.markRedundantOrderBy(orderBy);
warningCollector.add(
- new TrinoWarning(REDUNDANT_ORDER_BY, "ORDER BY in subquery may have no effect"));
+ new IoTDBWarning(REDUNDANT_ORDER_BY, "ORDER BY in subquery may have no effect"));
}
}
analysis.setOrderByExpressions(node, orderByExpressions);
@@ -623,63 +684,630 @@ public class StatementAnalyzer {
if (node.getLimit().isPresent()) {
boolean requiresOrderBy = analyzeLimit(node.getLimit().get(), outputScope);
- if (requiresOrderBy && node.getOrderBy().isEmpty()) {
- throw semanticException(
- MISSING_ORDER_BY,
- node.getLimit().get(),
- "FETCH FIRST WITH TIES clause requires ORDER BY");
+ if (requiresOrderBy && !node.getOrderBy().isPresent()) {
+ throw new SemanticException("FETCH FIRST WITH TIES clause requires ORDER BY");
}
}
List<Expression> sourceExpressions = new ArrayList<>();
analysis.getSelectExpressions(node).stream()
- .map(SelectExpression::getExpression)
+ .map(Analysis.SelectExpression::getExpression)
.forEach(sourceExpressions::add);
node.getHaving().ifPresent(sourceExpressions::add);
- for (WindowDefinition windowDefinition : node.getWindows()) {
- WindowSpecification window = windowDefinition.getWindow();
- sourceExpressions.addAll(window.getPartitionBy());
- getSortItemsFromOrderBy(window.getOrderBy()).stream()
- .map(SortItem::getSortKey)
- .forEach(sourceExpressions::add);
- if (window.getFrame().isPresent()) {
- WindowFrame frame = window.getFrame().get();
- frame.getStart().getValue().ifPresent(sourceExpressions::add);
- frame.getEnd().flatMap(FrameBound::getValue).ifPresent(sourceExpressions::add);
- frame.getMeasures().stream()
- .map(MeasureDefinition::getExpression)
- .forEach(sourceExpressions::add);
- frame.getVariableDefinitions().stream()
- .map(VariableDefinition::getExpression)
- .forEach(sourceExpressions::add);
- }
- }
- analyzeGroupingOperations(node, sourceExpressions, orderByExpressions);
analyzeAggregations(
node, sourceScope, orderByScope, groupByAnalysis, sourceExpressions, orderByExpressions);
- analyzeWindowFunctionsAndMeasures(node, outputExpressions, orderByExpressions);
if (analysis.isAggregation(node) && node.getOrderBy().isPresent()) {
ImmutableList.Builder<Expression> aggregates =
ImmutableList.<Expression>builder()
.addAll(groupByAnalysis.getOriginalExpressions())
- .addAll(
- extractAggregateFunctions(
- orderByExpressions, session, functionResolver, accessControl))
- .addAll(extractExpressions(orderByExpressions, GroupingOperation.class));
+ .addAll(extractAggregateFunctions(orderByExpressions));
analysis.setOrderByAggregates(node.getOrderBy().get(), aggregates.build());
}
if (node.getOrderBy().isPresent() && node.getSelect().isDistinct()) {
verifySelectDistinct(
- node, orderByExpressions, outputExpressions, sourceScope, orderByScope.orElseThrow());
+ node,
+ orderByExpressions,
+ outputExpressions,
+ sourceScope,
+ orderByScope.orElseThrow(() -> new NoSuchElementException("No value present")));
}
return outputScope;
}
+ private Scope analyzeFrom(QuerySpecification node, Optional<Scope> scope) {
+ if (node.getFrom().isPresent()) {
+ return process(node.getFrom().get(), scope);
+ }
+
+ Scope result = createScope(scope);
+ analysis.setImplicitFromScope(node, result);
+ return result;
+ }
+
+ private void analyzeWhere(Node node, Scope scope, Expression predicate) {
+ verifyNoAggregateWindowOrGroupingFunctions(predicate, "WHERE clause");
+
+ ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, scope);
+ analysis.recordSubqueries(node, expressionAnalysis);
+
+ Type predicateType = expressionAnalysis.getType(predicate);
+ if (!predicateType.equals(BOOLEAN)) {
+ // if (!predicateType.equals(UNKNOWN)) {
+ throw new SemanticException(
+ String.format(
+ "WHERE clause must evaluate to a boolean: actual type %s", predicateType));
+ // }
+ // coerce null to boolean
+ // analysis.addCoercion(predicate, BOOLEAN, false);
+ }
+
+ analysis.setWhere(node, predicate);
+ }
+
+ private List<Expression> analyzeSelect(QuerySpecification node, Scope scope) {
+ ImmutableList.Builder<Expression> outputExpressionBuilder = ImmutableList.builder();
+ ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder =
+ ImmutableList.builder();
+
+ for (SelectItem item : node.getSelect().getSelectItems()) {
+ if (item instanceof AllColumns) {
+ analyzeSelectAllColumns(
+ (AllColumns) item, node, scope, outputExpressionBuilder, selectExpressionBuilder);
+ } else if (item instanceof SingleColumn) {
+ analyzeSelectSingleColumn(
+ (SingleColumn) item, node, scope, outputExpressionBuilder, selectExpressionBuilder);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported SelectItem type: " + item.getClass().getName());
+ }
+ }
+ analysis.setSelectExpressions(node, selectExpressionBuilder.build());
+
+ return outputExpressionBuilder.build();
+ }
+
+ private void analyzeSelectAllColumns(
+ AllColumns allColumns,
+ QuerySpecification node,
+ Scope scope,
+ ImmutableList.Builder<Expression> outputExpressionBuilder,
+ ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+ // expand * and expression.*
+ if (allColumns.getTarget().isPresent()) {
+ // analyze AllColumns with target expression (expression.*)
+ Expression expression = allColumns.getTarget().get();
+
+ QualifiedName prefix = asQualifiedName(expression);
+ if (prefix != null) {
+ // analyze prefix as an 'asterisked identifier chain'
+ Scope.AsteriskedIdentifierChainBasis identifierChainBasis =
+ scope
+ .resolveAsteriskedIdentifierChainBasis(prefix, allColumns)
+ .orElseThrow(
+ () ->
+ new SemanticException(
+ String.format("Unable to resolve reference %s", prefix)));
+ if (identifierChainBasis.getBasisType() == TABLE) {
+ RelationType relationType =
+ identifierChainBasis
+ .getRelationType()
+ .orElseThrow(() -> new NoSuchElementException("No value present"));
+ List<Field> requestedFields =
+ relationType.resolveVisibleFieldsWithRelationPrefix(Optional.of(prefix));
+ List<Field> fields = filterInaccessibleFields(requestedFields);
+ if (fields.isEmpty()) {
+ if (!requestedFields.isEmpty()) {
+ throw new SemanticException("Relation not found or not allowed");
+ }
+ throw new SemanticException("SELECT * not allowed from relation that has no columns");
+ }
+ boolean local =
+ scope.isLocalScope(
+ identifierChainBasis
+ .getScope()
+ .orElseThrow(() -> new NoSuchElementException("No value present")));
+ analyzeAllColumnsFromTable(
+ fields,
+ allColumns,
+ node,
+ local ? scope : identifierChainBasis.getScope().get(),
+ outputExpressionBuilder,
+ selectExpressionBuilder,
+ relationType,
+ local);
+ return;
+ }
+ }
+ // identifierChainBasis.get().getBasisType == FIELD or target expression isn't a
+ // QualifiedName
+ throw new SemanticException(
+ "identifierChainBasis.get().getBasisType == FIELD or target expression isn't a QualifiedName");
+ // analyzeAllFieldsFromRowTypeExpression(expression, allColumns, node, scope,
+ // outputExpressionBuilder,
+ // selectExpressionBuilder);
+ } else {
+ // analyze AllColumns without target expression ('*')
+ if (!allColumns.getAliases().isEmpty()) {
+ throw new SemanticException("Column aliases not supported");
+ }
+
+ List<Field> requestedFields = (List<Field>) scope.getRelationType().getVisibleFields();
+ List<Field> fields = filterInaccessibleFields(requestedFields);
+ if (fields.isEmpty()) {
+ if (!node.getFrom().isPresent()) {
+ throw new SemanticException("SELECT * not allowed in queries without FROM clause");
+ }
+ if (!requestedFields.isEmpty()) {
+ throw new SemanticException("Relation not found or not allowed");
+ }
+ throw new SemanticException("SELECT * not allowed from relation that has no columns");
+ }
+
+ analyzeAllColumnsFromTable(
+ fields,
+ allColumns,
+ node,
+ scope,
+ outputExpressionBuilder,
+ selectExpressionBuilder,
+ scope.getRelationType(),
+ true);
+ }
+ }
+
+ private List<Field> filterInaccessibleFields(List<Field> fields) {
+
+ ImmutableSet.Builder<Field> accessibleFields = ImmutableSet.builder();
+
+ // collect fields by table
+ ListMultimap<QualifiedObjectName, Field> tableFieldsMap = ArrayListMultimap.create();
+ fields.forEach(
+ field -> {
+ Optional<QualifiedObjectName> originTable = field.getOriginTable();
+ if (originTable.isPresent()) {
+ tableFieldsMap.put(originTable.get(), field);
+ } else {
+ // keep anonymous fields accessible
+ accessibleFields.add(field);
+ }
+ });
+
+ // TODO Auth control
+ // tableFieldsMap.asMap().forEach((table, tableFields) -> {
+ // Set<String> accessibleColumns = accessControl.filterColumns(
+ // session.toSecurityContext(),
+ // table.getCatalogName(),
+ // ImmutableMap.of(
+ // table.asSchemaTableName(),
+ // tableFields.stream()
+ // .map(field -> field.getOriginColumnName().get())
+ // .collect(toImmutableSet())))
+ // .getOrDefault(table.asSchemaTableName(), ImmutableSet.of());
+ // accessibleFields.addAll(tableFields.stream()
+ // .filter(field -> accessibleColumns.contains(field.getOriginColumnName().get()))
+ // .collect(toImmutableList()));
+ // });
+
+ return fields.stream().filter(accessibleFields.build()::contains).collect(toImmutableList());
+ }
+
+ private void analyzeAllColumnsFromTable(
+ List<Field> fields,
+ AllColumns allColumns,
+ QuerySpecification node,
+ Scope scope,
+ ImmutableList.Builder<Expression> outputExpressionBuilder,
+ ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder,
+ RelationType relationType,
+ boolean local) {
+ if (!allColumns.getAliases().isEmpty()) {
+ validateColumnAliasesCount(allColumns.getAliases(), fields.size());
+ }
+
+ ImmutableList.Builder<Field> itemOutputFieldBuilder = ImmutableList.builder();
+
+ for (int i = 0; i < fields.size(); i++) {
+ Field field = fields.get(i);
+ Expression fieldExpression;
+ if (local) {
+ fieldExpression = new FieldReference(relationType.indexOf(field));
+ } else {
+ if (!field.getName().isPresent()) {
+ throw new SemanticException(
+ "SELECT * from outer scope table not supported with anonymous columns");
+ }
+ checkState(field.getRelationAlias().isPresent(), "missing relation alias");
+ fieldExpression =
+ new DereferenceExpression(
+ DereferenceExpression.from(field.getRelationAlias().get()),
+ new Identifier(field.getName().get()));
+ }
+ analyzeExpression(fieldExpression, scope);
+ outputExpressionBuilder.add(fieldExpression);
+ selectExpressionBuilder.add(
+ new Analysis.SelectExpression(fieldExpression, Optional.empty()));
+
+ Optional<String> alias = field.getName();
+ if (!allColumns.getAliases().isEmpty()) {
+ alias = Optional.of(allColumns.getAliases().get(i).getValue());
+ }
+
+ Field newField =
+ new Field(
+ field.getRelationAlias(),
+ alias,
+ field.getType(),
+ false,
+ field.getOriginTable(),
+ field.getOriginColumnName(),
+ !allColumns.getAliases().isEmpty() || field.isAliased());
+ itemOutputFieldBuilder.add(newField);
+ analysis.addSourceColumns(newField, analysis.getSourceColumns(field));
+
+ Type type = field.getType();
+ if (node.getSelect().isDistinct() && !type.isComparable()) {
+ throw new SemanticException(
+ String.format("DISTINCT can only be applied to comparable types (actual: %s)", type));
+ }
+ }
+ analysis.setSelectAllResultFields(allColumns, itemOutputFieldBuilder.build());
+ }
+
+ // private void analyzeAllFieldsFromRowTypeExpression(
+ // Expression expression,
+ // AllColumns allColumns,
+ // QuerySpecification node,
+ // Scope scope,
+ // ImmutableList.Builder<Expression> outputExpressionBuilder,
+ // ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+ // ImmutableList.Builder<Field> itemOutputFieldBuilder = ImmutableList.builder();
+ //
+ // ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, scope);
+ // Type type = expressionAnalysis.getType(expression);
+ // if (!(type instanceof RowType)) {
+ // throw semanticException(TYPE_MISMATCH, node.getSelect(), "expected expression of type
+ // Row");
+ // }
+ // int referencedFieldsCount = ((RowType) type).getFields().size();
+ // if (!allColumns.getAliases().isEmpty()) {
+ // validateColumnAliasesCount(allColumns.getAliases(), referencedFieldsCount);
+ // }
+ // analysis.recordSubqueries(node, expressionAnalysis);
+ //
+ // ImmutableList.Builder<Expression> unfoldedExpressionsBuilder = ImmutableList.builder();
+ // for (int i = 0; i < referencedFieldsCount; i++) {
+ // Expression outputExpression = new SubscriptExpression(expression, new LongLiteral("" +
+ // (i + 1)));
+ // outputExpressionBuilder.add(outputExpression);
+ // analyzeExpression(outputExpression, scope);
+ // unfoldedExpressionsBuilder.add(outputExpression);
+ //
+ // Type outputExpressionType = type.getTypeParameters().get(i);
+ // if (node.getSelect().isDistinct() && !outputExpressionType.isComparable()) {
+ // throw semanticException(TYPE_MISMATCH, node.getSelect(),
+ // "DISTINCT can only be applied to comparable types (actual: %s)",
+ // type.getTypeParameters().get(i));
+ // }
+ //
+ // Optional<String> name = ((RowType) type).getFields().get(i).getName();
+ // if (!allColumns.getAliases().isEmpty()) {
+ // name = Optional.of(allColumns.getAliases().get(i).getValue());
+ // }
+ // itemOutputFieldBuilder.add(Field.newUnqualified(name, outputExpressionType));
+ // }
+ // selectExpressionBuilder.add(new SelectExpression(expression,
+ // Optional.of(unfoldedExpressionsBuilder.build())));
+ // analysis.setSelectAllResultFields(allColumns, itemOutputFieldBuilder.build());
+ // }
+
+ private void analyzeSelectSingleColumn(
+ SingleColumn singleColumn,
+ QuerySpecification node,
+ Scope scope,
+ ImmutableList.Builder<Expression> outputExpressionBuilder,
+ ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+ Expression expression = singleColumn.getExpression();
+ ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, scope);
+ analysis.recordSubqueries(node, expressionAnalysis);
+ outputExpressionBuilder.add(expression);
+ selectExpressionBuilder.add(new Analysis.SelectExpression(expression, Optional.empty()));
+
+ Type type = expressionAnalysis.getType(expression);
+ if (node.getSelect().isDistinct() && !type.isComparable()) {
+ throw new SemanticException(
+ String.format(
+ "DISTINCT can only be applied to comparable types (actual: %s): %s",
+ type, expression));
+ }
+ }
+
+ private Analysis.GroupingSetAnalysis analyzeGroupBy(
+ QuerySpecification node, Scope scope, List<Expression> outputExpressions) {
+ if (node.getGroupBy().isPresent()) {
+ ImmutableList.Builder<List<Set<FieldId>>> cubes = ImmutableList.builder();
+ ImmutableList.Builder<List<Set<FieldId>>> rollups = ImmutableList.builder();
+ ImmutableList.Builder<List<Set<FieldId>>> sets = ImmutableList.builder();
+ ImmutableList.Builder<Expression> complexExpressions = ImmutableList.builder();
+ ImmutableList.Builder<Expression> groupingExpressions = ImmutableList.builder();
+
+ checkGroupingSetsCount(node.getGroupBy().get());
+ for (GroupingElement groupingElement : node.getGroupBy().get().getGroupingElements()) {
+ if (groupingElement instanceof SimpleGroupBy) {
+ for (Expression column : groupingElement.getExpressions()) {
+ // simple GROUP BY expressions allow ordinals or arbitrary expressions
+ if (column instanceof LongLiteral) {
+ long ordinal = ((LongLiteral) column).getParsedValue();
+ if (ordinal < 1 || ordinal > outputExpressions.size()) {
+ throw new SemanticException(
+ String.format("GROUP BY position %s is not in select list", ordinal));
+ }
+
+ column = outputExpressions.get(toIntExact(ordinal - 1));
+ verifyNoAggregateWindowOrGroupingFunctions(column, "GROUP BY clause");
+ } else {
+ verifyNoAggregateWindowOrGroupingFunctions(column, "GROUP BY clause");
+ analyzeExpression(column, scope);
+ }
+
+ ResolvedField field = analysis.getColumnReferenceFields().get(NodeRef.of(column));
+ if (field != null) {
+ sets.add(ImmutableList.of(ImmutableSet.of(field.getFieldId())));
+ } else {
+ analysis.recordSubqueries(node, analyzeExpression(column, scope));
+ complexExpressions.add(column);
+ }
+
+ groupingExpressions.add(column);
+ }
+ } else if (groupingElement instanceof GroupingSets) {
+ GroupingSets element = (GroupingSets) groupingElement;
+ for (Expression column : groupingElement.getExpressions()) {
+ analyzeExpression(column, scope);
+ if (!analysis.getColumnReferences().contains(NodeRef.of(column))) {
+ throw new SemanticException(
+ String.format("GROUP BY expression must be a column reference: %s", column));
+ }
+
+ groupingExpressions.add(column);
+ }
+
+ List<Set<FieldId>> groupingSets =
+ element.getSets().stream()
+ .map(
+ set ->
+ set.stream()
+ .map(NodeRef::of)
+ .map(analysis.getColumnReferenceFields()::get)
+ .map(ResolvedField::getFieldId)
+ .collect(toImmutableSet()))
+ .collect(toImmutableList());
+
+ switch (element.getType()) {
+ case CUBE:
+ cubes.add(groupingSets);
+ break;
+ case ROLLUP:
+ rollups.add(groupingSets);
+ break;
+ case EXPLICIT:
+ sets.add(groupingSets);
+ break;
+ }
+ }
+ }
+
+ List<Expression> expressions = groupingExpressions.build();
+ for (Expression expression : expressions) {
+ Type type = analysis.getType(expression);
+ if (!type.isComparable()) {
+ throw new SemanticException(
+ String.format(
+ "%s is not comparable, and therefore cannot be used in GROUP BY", type));
+ }
+ }
+
+ Analysis.GroupingSetAnalysis groupingSets =
+ new Analysis.GroupingSetAnalysis(
+ expressions,
+ cubes.build(),
+ rollups.build(),
+ sets.build(),
+ complexExpressions.build());
+ analysis.setGroupingSets(node, groupingSets);
+
+ return groupingSets;
+ }
+
+ Analysis.GroupingSetAnalysis result =
+ new Analysis.GroupingSetAnalysis(
+ ImmutableList.of(),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ ImmutableList.of());
+
+ if (hasAggregates(node) || node.getHaving().isPresent()) {
+ analysis.setGroupingSets(node, result);
+ }
+
+ return result;
+ }
+
+ private boolean hasAggregates(QuerySpecification node) {
+ List<Node> toExtract =
+ ImmutableList.<Node>builder()
+ .addAll(node.getSelect().getSelectItems())
+ .addAll(getSortItemsFromOrderBy(node.getOrderBy()))
+ .build();
+
+ List<FunctionCall> aggregates = extractAggregateFunctions(toExtract);
+
+ return !aggregates.isEmpty();
+ }
+
+ private void checkGroupingSetsCount(GroupBy node) {
+ // If groupBy is distinct then crossProduct will be overestimated if there are duplicate
+ // grouping sets.
+ int crossProduct = 1;
+ for (GroupingElement element : node.getGroupingElements()) {
+ try {
+ int product = 0;
+ if (element instanceof SimpleGroupBy) {
+ product = 1;
+ } else if (element instanceof GroupingSets) {
+ GroupingSets groupingSets = (GroupingSets) element;
+ switch (groupingSets.getType()) {
+ case CUBE:
+ int exponent = ((GroupingSets) element).getSets().size();
+ if (exponent > 30) {
+ throw new ArithmeticException();
+ }
+ product = 1 << exponent;
+ break;
+ case ROLLUP:
+ product = groupingSets.getSets().size() + 1;
+ break;
+ case EXPLICIT:
+ product = groupingSets.getSets().size();
+ break;
+ }
+ } else {
+ throw new UnsupportedOperationException(
+ "Unsupported grouping element type: " + element.getClass().getName());
+ }
+ crossProduct = Math.multiplyExact(crossProduct, product);
+ } catch (ArithmeticException e) {
+ throw new SemanticException(
+ String.format("GROUP BY has more than %s grouping sets", Integer.MAX_VALUE));
+ }
+ // if (crossProduct > getMaxGroupingSets(session)) {
+ // throw semanticException(TOO_MANY_GROUPING_SETS, node,
+ // "GROUP BY has %s grouping sets but can contain at most %s", crossProduct,
+ // getMaxGroupingSets(session));
+ // }
+ }
+ }
+
+ private void analyzeHaving(QuerySpecification node, Scope scope) {
+ if (node.getHaving().isPresent()) {
+ Expression predicate = node.getHaving().get();
+
+ ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, scope);
+ analysis.recordSubqueries(node, expressionAnalysis);
+
+ Type predicateType = expressionAnalysis.getType(predicate);
+ if (!predicateType.equals(BOOLEAN)) {
+ throw new SemanticException(
+ String.format(
+ "HAVING clause must evaluate to a boolean: actual type %s", predicateType));
+ }
+
+ analysis.setHaving(node, predicate);
+ }
+ }
+
+ private Scope computeAndAssignOutputScope(
+ QuerySpecification node, Optional<Scope> scope, Scope sourceScope) {
+ ImmutableList.Builder<Field> outputFields = ImmutableList.builder();
+
+ for (SelectItem item : node.getSelect().getSelectItems()) {
+ if (item instanceof AllColumns) {
+ AllColumns allColumns = (AllColumns) item;
+ List<Field> fields = analysis.getSelectAllResultFields(allColumns);
+ checkNotNull(fields, "output fields is null for select item %s", item);
+ for (int i = 0; i < fields.size(); i++) {
+ Field field = fields.get(i);
+
+ Optional<String> name;
+ if (!allColumns.getAliases().isEmpty()) {
+ name = Optional.of(allColumns.getAliases().get(i).getCanonicalValue());
+ } else {
+ name = field.getName();
+ }
+
+ Field newField =
+ Field.newUnqualified(
+ name,
+ field.getType(),
+ field.getOriginTable(),
+ field.getOriginColumnName(),
+ false);
+ analysis.addSourceColumns(newField, analysis.getSourceColumns(field));
+ outputFields.add(newField);
+ }
+ } else if (item instanceof SingleColumn) {
+ SingleColumn column = (SingleColumn) item;
+ Expression expression = column.getExpression();
+ Optional<Identifier> field = column.getAlias();
+
+ Optional<QualifiedObjectName> originTable = Optional.empty();
+ Optional<String> originColumn = Optional.empty();
+ QualifiedName name = null;
+
+ if (expression instanceof Identifier) {
+ name = QualifiedName.of(((Identifier) expression).getValue());
+ } else if (expression instanceof DereferenceExpression) {
+ name = DereferenceExpression.getQualifiedName((DereferenceExpression) expression);
+ }
+
+ if (name != null) {
+ List<Field> matchingFields = sourceScope.getRelationType().resolveFields(name);
+ if (!matchingFields.isEmpty()) {
+ originTable = matchingFields.get(0).getOriginTable();
+ originColumn = matchingFields.get(0).getOriginColumnName();
+ }
+ }
+
+ if (!field.isPresent() && (name != null)) {
+ field = Optional.of(getLast(name.getOriginalParts()));
+ }
+
+ Field newField =
+ Field.newUnqualified(
+ field.map(Identifier::getValue),
+ analysis.getType(expression),
+ originTable,
+ originColumn,
+ column.getAlias().isPresent()); // TODO don't use analysis as a side-channel. Use
+ // outputExpressions to look up the type
+ if (originTable.isPresent()) {
+ analysis.addSourceColumns(
+ newField,
+ ImmutableSet.of(
+ new Analysis.SourceColumn(
+ originTable.get(),
+ originColumn.orElseThrow(
+ () -> new NoSuchElementException("No value present")))));
+ } else {
+ analysis.addSourceColumns(newField, analysis.getExpressionSourceColumns(expression));
+ }
+ outputFields.add(newField);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported SelectItem type: " + item.getClass().getName());
+ }
+ }
+
+ return createAndAssignScope(node, scope, outputFields.build());
+ }
+
+ private Scope computeAndAssignOrderByScope(OrderBy node, Scope sourceScope, Scope outputScope) {
+ // ORDER BY should "see" both output and FROM fields during initial analysis and
+ // non-aggregation query planning
+ Scope orderByScope =
+ Scope.builder()
+ .withParent(sourceScope)
+ .withRelationType(outputScope.getRelationId(), outputScope.getRelationType())
+ .build();
+ analysis.setScope(node, orderByScope);
+ return orderByScope;
+ }
+
@Override
protected Scope visitSubqueryExpression(SubqueryExpression node, Optional<Scope> context) {
return process(node.getQuery(), context);
@@ -701,29 +1329,22 @@ public class StatementAnalyzer {
int outputFieldSize = outputFieldTypes.length;
int descFieldSize = relationType.getVisibleFields().size();
if (outputFieldSize != descFieldSize) {
- throw semanticException(
- TYPE_MISMATCH,
- node,
- "%s query has different number of fields: %d, %d",
- setOperationName,
- outputFieldSize,
- descFieldSize);
+ throw new SemanticException(
+ String.format(
+ "%s query has different number of fields: %d, %d",
+ setOperationName, outputFieldSize, descFieldSize));
}
for (int i = 0; i < descFieldSize; i++) {
Type descFieldType = relationType.getFieldByIndex(i).getType();
- Optional<Type> commonSuperType =
- typeCoercion.getCommonSuperType(outputFieldTypes[i], descFieldType);
- if (commonSuperType.isEmpty()) {
- throw semanticException(
- TYPE_MISMATCH,
- node,
- "column %d in %s query has incompatible types: %s, %s",
- i + 1,
- setOperationName,
- outputFieldTypes[i].getDisplayName(),
- descFieldType.getDisplayName());
+ if (descFieldType != outputFieldTypes[i]) {
+ throw new SemanticException(
+ String.format(
+ "column %d in %s query has incompatible types: %s, %s",
+ i + 1,
+ setOperationName,
+ outputFieldTypes[i].getDisplayName(),
+ descFieldType.getDisplayName()));
}
- outputFieldTypes[i] = commonSuperType.get();
}
}
@@ -732,13 +1353,10 @@ public class StatementAnalyzer {
|| node instanceof Union && node.isDistinct()) {
for (Type type : outputFieldTypes) {
if (!type.isComparable()) {
- throw semanticException(
- TYPE_MISMATCH,
- node,
- "Type %s is not comparable and therefore cannot be used in %s%s",
- type,
- setOperationName,
- node instanceof Union ? " DISTINCT" : "");
+ throw new SemanticException(
+ String.format(
+ "Type %s is not comparable and therefore cannot be used in %s%s",
+ type, setOperationName, node instanceof Union ? " DISTINCT" : ""));
}
}
}
@@ -782,64 +1400,328 @@ public class StatementAnalyzer {
}
@Override
- protected Scope visitJoin(Join node, Optional<Scope> scope) {
- JoinCriteria criteria = node.getCriteria().orElse(null);
- if (criteria instanceof NaturalJoin) {
- throw semanticException(NOT_SUPPORTED, node, "Natural join not supported");
+ protected Scope visitTable(Table table, Optional<Scope> scope) {
+ if (!table.getName().getPrefix().isPresent()) {
+ // is this a reference to a WITH query?
+ Optional<WithQuery> withQuery =
+ createScope(scope).getNamedQuery(table.getName().getSuffix());
+ if (withQuery.isPresent()) {
+ analysis.setRelationName(table, table.getName());
+ return createScopeForCommonTableExpression(table, scope, withQuery.get());
+ }
+ // is this a recursive reference in expandable WITH query? If so, there's base scope
+ // recorded.
+ Optional<Scope> expandableBaseScope = analysis.getExpandableBaseScope(table);
+ if (expandableBaseScope.isPresent()) {
+ Scope baseScope = expandableBaseScope.get();
+ // adjust local and outer parent scopes accordingly to the local context of the recursive
+ // reference
+ Scope resultScope =
+ scopeBuilder(scope)
+ .withRelationType(baseScope.getRelationId(), baseScope.getRelationType())
+ .build();
+ analysis.setScope(table, resultScope);
+ analysis.setRelationName(table, table.getName());
+ return resultScope;
+ }
}
- Scope left = process(node.getLeft(), scope);
- Scope right =
- process(node.getRight(), isLateralRelation(node.getRight()) ? Optional.of(left) : scope);
-
- if (isLateralRelation(node.getRight())) {
- if (node.getType() == RIGHT || node.getType() == FULL) {
- Stream<Expression> leftScopeReferences =
- getReferencesToScope(node.getRight(), analysis, left);
- leftScopeReferences
- .findFirst()
- .ifPresent(
- reference -> {
- throw semanticException(
- INVALID_COLUMN_REFERENCE,
- reference,
- "LATERAL reference not allowed in %s JOIN",
- node.getType().name());
- });
- }
- if (isUnnestRelation(node.getRight())) {
- if (criteria != null) {
- if (!(criteria instanceof JoinOn)
- || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
- throw semanticException(
- NOT_SUPPORTED,
- criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
- "%s JOIN involving UNNEST is only supported with condition ON TRUE",
- node.getType().name());
- }
- }
- } else if (isJsonTable(node.getRight())) {
- if (criteria != null) {
- if (!(criteria instanceof JoinOn)
- || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
- throw semanticException(
- NOT_SUPPORTED,
- criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
- "%s JOIN involving JSON_TABLE is only supported with condition ON TRUE",
- node.getType().name());
- }
+ QualifiedObjectName name = createQualifiedObjectName(sessionContext, table, table.getName());
+ analysis.setRelationName(
+ table, QualifiedName.of(name.getDatabaseName(), name.getObjectName()));
+
+ Optional<TableHandle> tableHandle = metadata.getTableHandle(sessionContext, name);
+ // This can only be a table
+ if (!tableHandle.isPresent()) {
+ throw new SemanticException(String.format("Table '%s' does not exist", name));
+ }
+ analysis.addEmptyColumnReferencesForTable(accessControl, sessionContext.getIdentity(), name);
+
+ TableSchema tableSchema = metadata.getTableSchema(sessionContext, tableHandle.get());
+ Map<String, ColumnHandle> columnHandles =
+ metadata.getColumnHandles(sessionContext, tableHandle.get());
+
+ ImmutableList.Builder<Field> fields = ImmutableList.builder();
+ fields.addAll(analyzeTableOutputFields(table, name, tableSchema, columnHandles));
+
+ // boolean addRowIdColumn = updateKind.isPresent();
+ //
+ // if (addRowIdColumn) {
+ // // Add the row id field
+ // ColumnHandle rowIdColumnHandle = metadata.getMergeRowIdColumnHandle(session,
+ // tableHandle.get());
+ // Type type = metadata.getColumnMetadata(session, tableHandle.get(),
+ // rowIdColumnHandle).getType();
+ // Field field = Field.newUnqualified(Optional.empty(), type);
+ // fields.add(field);
+ // analysis.setColumn(field, rowIdColumnHandle);
+ // }
+
+ List<Field> outputFields = fields.build();
+
+ Scope accessControlScope =
+ Scope.builder()
+ .withRelationType(RelationId.anonymous(), new RelationType(outputFields))
+ .build();
+ // analyzeFiltersAndMasks(table, name, new RelationType(outputFields),
+ // accessControlScope);
+ analysis.registerTable(table, tableHandle, name);
+
+ Scope tableScope = createAndAssignScope(table, scope, outputFields);
+
+ // if (addRowIdColumn) {
+ // FieldReference reference = new FieldReference(outputFields.size() - 1);
+ // analyzeExpression(reference, tableScope);
+ // analysis.setRowIdField(table, reference);
+ // }
+
+ return tableScope;
+ }
+
+ private Scope createScopeForCommonTableExpression(
+ Table table, Optional<Scope> scope, WithQuery withQuery) {
+ Query query = withQuery.getQuery();
+ analysis.registerNamedQuery(table, query);
+
+ // re-alias the fields with the name assigned to the query in the WITH declaration
+ RelationType queryDescriptor = analysis.getOutputDescriptor(query);
+
+ List<Field> fields;
+ Optional<List<Identifier>> columnNames = withQuery.getColumnNames();
+ if (columnNames.isPresent()) {
+ // if columns are explicitly aliased -> WITH cte(alias1, alias2 ...)
+ checkState(
+ columnNames.get().size() == queryDescriptor.getVisibleFieldCount(),
+ "mismatched aliases");
+ ImmutableList.Builder<Field> fieldBuilder = ImmutableList.builder();
+ Iterator<Identifier> aliases = columnNames.get().iterator();
+ for (int i = 0; i < queryDescriptor.getAllFieldCount(); i++) {
+ Field inputField = queryDescriptor.getFieldByIndex(i);
+ if (!inputField.isHidden()) {
+ Field field =
+ Field.newQualified(
+ QualifiedName.of(table.getName().getSuffix()),
+ Optional.of(aliases.next().getValue()),
+ inputField.getType(),
+ false,
+ inputField.getOriginTable(),
+ inputField.getOriginColumnName(),
+ inputField.isAliased());
+ fieldBuilder.add(field);
+ analysis.addSourceColumns(field, analysis.getSourceColumns(inputField));
}
- } else if (node.getType() == FULL) {
- if (!(criteria instanceof JoinOn)
- || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
- throw semanticException(
- NOT_SUPPORTED,
- criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
- "FULL JOIN involving LATERAL relation is only supported with condition ON TRUE");
+ }
+ fields = fieldBuilder.build();
+ } else {
+ ImmutableList.Builder<Field> fieldBuilder = ImmutableList.builder();
+ for (int i = 0; i < queryDescriptor.getAllFieldCount(); i++) {
+ Field inputField = queryDescriptor.getFieldByIndex(i);
+ if (!inputField.isHidden()) {
+ Field field =
+ Field.newQualified(
+ QualifiedName.of(table.getName().getSuffix()),
+ inputField.getName(),
+ inputField.getType(),
+ false,
+ inputField.getOriginTable(),
+ inputField.getOriginColumnName(),
+ inputField.isAliased());
+ fieldBuilder.add(field);
+ analysis.addSourceColumns(field, analysis.getSourceColumns(inputField));
}
}
+ fields = fieldBuilder.build();
+ }
+
+ return createAndAssignScope(table, scope, fields);
+ }
+
+ private List<Field> analyzeTableOutputFields(
+ Table table,
+ QualifiedObjectName tableName,
+ TableSchema tableSchema,
+ Map<String, ColumnHandle> columnHandles) {
+ // TODO: discover columns lazily based on where they are needed (to support connectors that
+ // can't enumerate all tables)
+ ImmutableList.Builder<Field> fields = ImmutableList.builder();
+ for (ColumnSchema column : tableSchema.getColumns()) {
+ Field field =
+ Field.newQualified(
+ table.getName(),
+ Optional.of(column.getName()),
+ column.getType(),
+ column.isHidden(),
+ Optional.of(tableName),
+ Optional.of(column.getName()),
+ false);
+ fields.add(field);
+ ColumnHandle columnHandle = columnHandles.get(column.getName());
+ checkArgument(columnHandle != null, "Unknown field %s", field);
+ analysis.setColumn(field, columnHandle);
+ analysis.addSourceColumns(
+ field, ImmutableSet.of(new Analysis.SourceColumn(tableName, column.getName())));
+ }
+ return fields.build();
+ }
+
+ // private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, RelationType
+ // relationType,
+ // Scope accessControlScope) {
+ // for (int index = 0; index < relationType.getAllFieldCount(); index++) {
+ // Field field = relationType.getFieldByIndex(index);
+ // if (field.getName().isPresent()) {
+ // Optional<ViewExpression> mask =
+ // accessControl.getColumnMask(session.toSecurityContext(), name,
+ // field.getName().get(), field.getType());
+ //
+ // if (mask.isPresent() && checkCanSelectFromColumn(name,
+ // field.getName().orElseThrow())) {
+ // analyzeColumnMask(session.getIdentity().getUser(), table, name, field,
+ // accessControlScope, mask.get());
+ // }
+ // }
+ // }
+ //
+ // accessControl.getRowFilters(session.toSecurityContext(), name)
+ // .forEach(
+ // filter -> analyzeRowFilter(session.getIdentity().getUser(), table, name,
+ // accessControlScope, filter));
+ // }
+
+ // @Override
+ // protected Scope visitValues(Values node, Optional<Scope> scope) {
+ // checkState(!node.getRows().isEmpty());
+ //
+ // List<Type> rowTypes = node.getRows().stream()
+ // .map(row -> analyzeExpression(row, createScope(scope)).getType(row))
+ // .map(type -> {
+ // if (type instanceof RowType) {
+ // return type;
+ // }
+ // return RowType.anonymousRow(type);
+ // })
+ // .collect(toImmutableList());
+ //
+ // int fieldCount = rowTypes.get(0).getTypeParameters().size();
+ // Type commonSuperType = rowTypes.get(0);
+ // for (Type rowType : rowTypes) {
+ // // check field count consistency for rows
+ // if (rowType.getTypeParameters().size() != fieldCount) {
+ // throw semanticException(TYPE_MISMATCH,
+ // node,
+ // "Values rows have mismatched sizes: %s vs %s",
+ // fieldCount,
+ // rowType.getTypeParameters().size());
+ // }
+ //
+ // // determine common super type of the rows
+ // commonSuperType = typeCoercion.getCommonSuperType(rowType, commonSuperType)
+ // .orElseThrow(() -> semanticException(TYPE_MISMATCH,
+ // node,
+ // "Values rows have mismatched types: %s vs %s",
+ // rowTypes.get(0),
+ // rowType));
+ // }
+ //
+ // // add coercions
+ // for (Expression row : node.getRows()) {
+ // Type actualType = analysis.getType(row);
+ // if (row instanceof Row) {
+ // // coerce Row by fields to preserve Row structure and enable optimizations based on
+ // this structure, e.g. pruning, predicate extraction
+ // // TODO coerce the whole Row and add an Optimizer rule that converts CAST(ROW(...)
+ // AS ...) into ROW(CAST(...), CAST(...), ...).
+ // // The rule would also handle Row-type expressions that were specified as
+ // CAST(ROW). It should support multiple casts over a ROW.
+ // for (int i = 0; i < actualType.getTypeParameters().size(); i++) {
+ // Expression item = ((Row) row).getItems().get(i);
+ // Type actualItemType = actualType.getTypeParameters().get(i);
+ // Type expectedItemType = commonSuperType.getTypeParameters().get(i);
+ // if (!actualItemType.equals(expectedItemType)) {
+ // analysis.addCoercion(item, expectedItemType,
+ // typeCoercion.isTypeOnlyCoercion(actualItemType, expectedItemType));
+ // }
+ // }
+ // } else if (actualType instanceof RowType) {
+ // // coerce row-type expression as a whole
+ // if (!actualType.equals(commonSuperType)) {
+ // analysis.addCoercion(row, commonSuperType,
+ // typeCoercion.isTypeOnlyCoercion(actualType, commonSuperType));
+ // }
+ // } else {
+ // // coerce field. it will be wrapped in Row by Planner
+ // Type superType = getOnlyElement(commonSuperType.getTypeParameters());
+ // if (!actualType.equals(superType)) {
+ // analysis.addCoercion(row, superType, typeCoercion.isTypeOnlyCoercion(actualType,
+ // superType));
+ // }
+ // }
+ // }
+ //
+ // List<Field> fields = commonSuperType.getTypeParameters().stream()
+ // .map(valueType -> Field.newUnqualified(Optional.empty(), valueType))
+ // .collect(toImmutableList());
+ //
+ // return createAndAssignScope(node, scope, fields);
+ // }
+
+ @Override
+ protected Scope visitAliasedRelation(AliasedRelation relation, Optional<Scope> scope) {
+ analysis.setRelationName(relation, QualifiedName.of(ImmutableList.of(relation.getAlias())));
+ analysis.addAliased(relation.getRelation());
+ Scope relationScope = process(relation.getRelation(), scope);
+ RelationType relationType = relationScope.getRelationType();
+
+ // todo this check should be inside of TupleDescriptor.withAlias, but the exception needs the
+ // node object
+ if (relation.getColumnNames() != null) {
+ int totalColumns = relationType.getVisibleFieldCount();
+ if (totalColumns != relation.getColumnNames().size()) {
+ throw new SemanticException(
+ String.format(
+ "Column alias list has %s entries but '%s' has %s columns available",
+ relation.getColumnNames().size(), relation.getAlias(), totalColumns));
+ }
+ }
+
+ List<String> aliases = null;
+ Collection<Field> inputFields = relationType.getAllFields();
+ if (relation.getColumnNames() != null) {
+ aliases =
+ relation.getColumnNames().stream()
+ .map(Identifier::getValue)
+ .collect(Collectors.toList());
+ // hidden fields are not exposed when there are column aliases
+ inputFields = relationType.getVisibleFields();
}
+ RelationType descriptor = relationType.withAlias(relation.getAlias().getValue(), aliases);
+
+ checkArgument(
+ inputFields.size() == descriptor.getAllFieldCount(),
+ "Expected %s fields, got %s",
+ descriptor.getAllFieldCount(),
+ inputFields.size());
+
+ Streams.forEachPair(
+ descriptor.getAllFields().stream(),
+ inputFields.stream(),
+ (newField, field) ->
+ analysis.addSourceColumns(newField, analysis.getSourceColumns(field)));
+
+ return createAndAssignScope(relation, scope, descriptor);
+ }
+
+ @Override
+ protected Scope visitJoin(Join node, Optional<Scope> scope) {
+ JoinCriteria criteria = node.getCriteria().orElse(null);
+ if (criteria instanceof NaturalJoin) {
+ throw new SemanticException("Natural join not supported");
+ }
+
+ Scope left = process(node.getLeft(), scope);
+ Scope right = process(node.getRight(), scope);
+
if (criteria instanceof JoinUsing) {
return analyzeJoinUsing(node, ((JoinUsing) criteria).getColumns(), scope, left, right);
}
@@ -853,8 +1735,7 @@ public class StatementAnalyzer {
}
if (criteria instanceof JoinOn) {
Expression expression = ((JoinOn) criteria).getExpression();
- verifyNoAggregateWindowOrGroupingFunctions(
- session, functionResolver, accessControl, expression, "JOIN clause");
+ verifyNoAggregateWindowOrGroupingFunctions(expression, "JOIN clause");
// Need to register coercions in case when join criteria requires coercion (e.g. join on
// char(1) = char(2))
@@ -868,15 +1749,18 @@ public class StatementAnalyzer {
: CorrelationSupport.DISALLOWED);
Type clauseType = expressionAnalysis.getType(expression);
if (!clauseType.equals(BOOLEAN)) {
- if (!clauseType.equals(UNKNOWN)) {
- throw semanticException(
- TYPE_MISMATCH,
- expression,
- "JOIN ON clause must evaluate to a boolean: actual type %s",
- clauseType);
- }
+ // if (!clauseType.equals(UNKNOWN)) {
+ // throw semanticException(
+ // TYPE_MISMATCH,
+ // expression,
+ // "JOIN ON clause must evaluate to a boolean: actual type %s",
+ // clauseType);
+ // }
+ throw new SemanticException(
+ String.format(
+ "JOIN ON clause must evaluate to a boolean: actual type %s", clauseType));
// coerce expression to boolean
- analysis.addCoercion(expression, BOOLEAN, false);
+ // analysis.addCoercion(expression, BOOLEAN, false);
}
analysis.recordSubqueries(node, expressionAnalysis);
@@ -889,6 +1773,100 @@ public class StatementAnalyzer {
return output;
}
+ private Scope analyzeJoinUsing(
+ Join node, List<Identifier> columns, Optional<Scope> scope, Scope left, Scope right) {
+ List<Field> joinFields = new ArrayList<>();
+
+ List<Integer> leftJoinFields = new ArrayList<>();
+ List<Integer> rightJoinFields = new ArrayList<>();
+
+ Set<Identifier> seen = new HashSet<>();
+ for (Identifier column : columns) {
+ if (!seen.add(column)) {
+ throw new SemanticException(
+ String.format(
+ "Column '%s' appears multiple times in USING clause", column.getValue()));
+ }
+
+ ResolvedField leftField =
+ left.tryResolveField(column)
+ .orElseThrow(
+ () ->
+ new SemanticException(
+ String.format(
+ "Column '%s' is missing from left side of join",
+ column.getValue())));
+ ResolvedField rightField =
+ right
+ .tryResolveField(column)
+ .orElseThrow(
+ () ->
+ new SemanticException(
+ String.format(
+ "Column '%s' is missing from right side of join",
+ column.getValue())));
+
+ // ensure a comparison operator exists for the given types (applying coercions if necessary)
+ // try {
+ // metadata.resolveOperator(OperatorType.EQUAL, ImmutableList.of(
+ // leftField.getType(), rightField.getType()));
+ // } catch (OperatorNotFoundException e) {
+ // throw semanticException(TYPE_MISMATCH, column, e, "%s", e.getMessage());
+ // }
+ if (leftField.getType() != rightField.getType()) {
+ throw new SemanticException(
+ String.format(
+ "Column Types of left and right side are different: left is %s, right is %s",
+ leftField.getType(), rightField.getType()));
+ }
+
+ analysis.addTypes(ImmutableMap.of(NodeRef.of(column), leftField.getType()));
+
+ joinFields.add(Field.newUnqualified(column.getValue(), leftField.getType()));
+
+ leftJoinFields.add(leftField.getRelationFieldIndex());
+ rightJoinFields.add(rightField.getRelationFieldIndex());
+
+ recordColumnAccess(leftField.getField());
+ recordColumnAccess(rightField.getField());
+ }
+
+ ImmutableList.Builder<Field> outputs = ImmutableList.builder();
+ outputs.addAll(joinFields);
+
+ ImmutableList.Builder<Integer> leftFields = ImmutableList.builder();
+ for (int i = 0; i < left.getRelationType().getAllFieldCount(); i++) {
+ if (!leftJoinFields.contains(i)) {
+ outputs.add(left.getRelationType().getFieldByIndex(i));
+ leftFields.add(i);
+ }
+ }
+
+ ImmutableList.Builder<Integer> rightFields = ImmutableList.builder();
+ for (int i = 0; i < right.getRelationType().getAllFieldCount(); i++) {
+ if (!rightJoinFields.contains(i)) {
+ outputs.add(right.getRelationType().getFieldByIndex(i));
+ rightFields.add(i);
+ }
+ }
+
+ analysis.setJoinUsing(
+ node,
+ new Analysis.JoinUsingAnalysis(
+ leftJoinFields, rightJoinFields, leftFields.build(), rightFields.build()));
+
+ return createAndAssignScope(node, scope, new RelationType(outputs.build()));
+ }
+
+ private void recordColumnAccess(Field field) {
+ if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) {
+ analysis.addTableColumnReferences(
+ accessControl,
+ sessionContext.getIdentity(),
+ ImmutableMultimap.of(field.getOriginTable().get(), field.getOriginColumnName().get()));
+ }
+ }
+
private List<Expression> analyzeOrderBy(
Node node, List<SortItem> sortItems, Scope orderByScope) {
ImmutableList.Builder<Expression> orderByFieldsBuilder = ImmutableList.builder();
@@ -910,9 +1888,7 @@ public class StatementAnalyzer {
ExpressionAnalysis expressionAnalysis =
ExpressionAnalyzer.analyzeExpression(
- session,
- plannerContext,
- statementAnalyzerFactory,
+ sessionContext,
accessControl,
orderByScope,
analysis,
@@ -940,12 +1916,15 @@ public class StatementAnalyzer {
if (node.getRowCount() instanceof LongLiteral) {
rowCount = ((LongLiteral) node.getRowCount()).getParsedValue();
} else {
- checkState(
- node.getRowCount() instanceof Parameter,
+ // checkState(
+ // node.getRowCount() instanceof Parameter,
+ // "unexpected OFFSET rowCount: " +
+ // node.getRowCount().getClass().getSimpleName());
+ throw new SemanticException(
"unexpected OFFSET rowCount: " + node.getRowCount().getClass().getSimpleName());
- OptionalLong providedValue =
- analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "OFFSET");
- rowCount = providedValue.orElse(0);
+ // OptionalLong providedValue =
+ // analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "OFFSET");
+ // rowCount = providedValue.orElse(0);
}
if (rowCount < 0) {
throw new SemanticException(
@@ -1006,10 +1985,14 @@ public class StatementAnalyzer {
} else if (node.getRowCount() instanceof LongLiteral) {
rowCount = OptionalLong.of(((LongLiteral) node.getRowCount()).getParsedValue());
} else {
- checkState(
- node.getRowCount() instanceof Parameter,
+ // checkState(
+ // node.getRowCount() instanceof Parameter,
+ // "unexpected LIMIT rowCount: " +
+ // node.getRowCount().getClass().getSimpleName());
+ throw new SemanticException(
"unexpected LIMIT rowCount: " + node.getRowCount().getClass().getSimpleName());
- rowCount = analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "LIMIT");
+ // rowCount = analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope,
+ // "LIMIT");
}
rowCount.ifPresent(
count -> {
@@ -1025,37 +2008,72 @@ public class StatementAnalyzer {
return false;
}
- private OptionalLong analyzeParameterAsRowCount(
- Parameter parameter, Scope scope, String context) {
- // validate parameter index
- analyzeExpression(parameter, scope);
- Expression providedValue = analysis.getParameters().get(NodeRef.of(parameter));
- Object value;
- try {
- value =
- evaluateConstantExpression(
- providedValue,
- BIGINT,
- plannerContext,
- session,
- accessControl,
- analysis.getParameters());
- } catch (VerifyException e) {
- throw new SemanticException(
- String.format("Non constant parameter value for %s: %s", context, providedValue));
- }
- if (value == null) {
- throw new SemanticException(
- String.format("Parameter value provided for %s is NULL: %s", context, providedValue));
+ // private OptionalLong analyzeParameterAsRowCount(
+ // Parameter parameter, Scope scope, String context) {
+ // // validate parameter index
+ // analyzeExpression(parameter, scope);
+ // Expression providedValue = analysis.getParameters().get(NodeRef.of(parameter));
+ // Object value;
+ // try {
+ // value =
+ // evaluateConstantExpression(
+ // providedValue,
+ // BIGINT,
+ // plannerContext,
+ // session,
+ // accessControl,
+ // analysis.getParameters());
+ // } catch (VerifyException e) {
+ // throw new SemanticException(
+ // String.format("Non constant parameter value for %s: %s", context, providedValue));
+ // }
+ // if (value == null) {
+ // throw new SemanticException(
+ // String.format("Parameter value provided for %s is NULL: %s", context,
+ // providedValue));
+ // }
+ // return OptionalLong.of((long) value);
+ // }
+
+ private void analyzeAggregations(
+ QuerySpecification node,
+ Scope sourceScope,
+ Optional<Scope> orderByScope,
+ Analysis.GroupingSetAnalysis groupByAnalysis,
+ List<Expression> outputExpressions,
+ List<Expression> orderByExpressions) {
+ checkState(
+ orderByExpressions.isEmpty() || orderByScope.isPresent(),
+ "non-empty orderByExpressions list without orderByScope provided");
+
+ List<FunctionCall> aggregates =
+ extractAggregateFunctions(Iterables.concat(outputExpressions, orderByExpressions));
+ analysis.setAggregates(node, aggregates);
+
+ if (analysis.isAggregation(node)) {
+ // ensure SELECT, ORDER BY and HAVING are constant with respect to group
+ // e.g, these are all valid expressions:
+ // SELECT f(a) GROUP BY a
+ // SELECT f(a + 1) GROUP BY a + 1
+ // SELECT a + sum(b) GROUP BY a
+ List<Expression> distinctGroupingColumns =
+ ImmutableSet.copyOf(groupByAnalysis.getOriginalExpressions()).asList();
+
+ verifySourceAggregations(distinctGroupingColumns, sourceScope, outputExpressions, analysis);
+ if (!orderByExpressions.isEmpty()) {
+ verifyOrderByAggregations(
+ distinctGroupingColumns,
+ sourceScope,
+ orderByScope.orElseThrow(() -> new NoSuchElementException("No value present")),
+ orderByExpressions,
+ analysis);
+ }
}
- return OptionalLong.of((long) value);
}
private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope) {
return ExpressionAnalyzer.analyzeExpression(
- session,
- plannerContext,
- statementAnalyzerFactory,
+ sessionContext,
accessControl,
scope,
analysis,
@@ -1067,9 +2085,7 @@ public class StatementAnalyzer {
private ExpressionAnalysis analyzeExpression(
Expression expression, Scope scope, CorrelationSupport correlationSupport) {
return ExpressionAnalyzer.analyzeExpression(
- session,
- plannerContext,
- statementAnalyzerFactory,
+ sessionContext,
accessControl,
scope,
analysis,
@@ -1383,4 +2399,15 @@ public class StatementAnalyzer {
return false;
}
+
+ static void verifyNoAggregateWindowOrGroupingFunctions(Expression predicate, String clause) {
+ List<FunctionCall> aggregates = extractAggregateFunctions(ImmutableList.of(predicate));
+
+ if (!aggregates.isEmpty()) {
+ throw new SemanticException(
+ String.format(
+ "%s cannot contain aggregations, window functions or grouping operations: %s",
+ clause, aggregates));
+ }
+ }
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
similarity index 57%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
index 61d95d2e832..b035782cfa8 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
@@ -17,6 +17,24 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
-public class ExpressionAnalyzer {}
+/**
+ * Represents a handle to a column within a {@link TableHandle} returned from the connector to the
+ * engine. It will be used by the engine whenever given column will be accessed.
+ *
+ * <p>ColumnHandle should follow:
+ *
+ * <ul>
+ * <li>a table cannot have two equal column handles
+ * <li>column handle is unique within a table scope. Two different tables can have two equal
+ * column handles
+ * </ul>
+ */
+public interface ColumnHandle {
+ @Override
+ int hashCode();
+
+ @Override
+ boolean equals(Object other);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java
new file mode 100644
index 00000000000..6039f6d1cf7
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java
@@ -0,0 +1,213 @@
+/*
+ * 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.metadata;
+
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+
+public class ColumnMetadata {
+ private final String name;
+ private final Type type;
+ private final boolean nullable;
+ private final String comment;
+ private final String extraInfo;
+ private final boolean hidden;
+ private final Map<String, Object> properties;
+
+ public ColumnMetadata(String name, Type type) {
+ this(name, type, true, null, null, false, emptyMap());
+ }
+
+ private ColumnMetadata(
+ String name,
+ Type type,
+ boolean nullable,
+ String comment,
+ String extraInfo,
+ boolean hidden,
+ Map<String, Object> properties) {
+ requireNonNull(type, "type is null");
+ requireNonNull(properties, "properties is null");
+
+ this.name = name.toLowerCase(ENGLISH);
+ this.type = type;
+ this.comment = comment;
+ this.extraInfo = extraInfo;
+ this.hidden = hidden;
+ this.properties =
+ properties.isEmpty() ? emptyMap() : unmodifiableMap(new LinkedHashMap<>(properties));
+ this.nullable = nullable;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public boolean isNullable() {
+ return nullable;
+ }
+
+ public Optional<String> getComment() {
+ return Optional.ofNullable(comment);
+ }
+
+ public Optional<String> getExtraInfo() {
+ return Optional.ofNullable(extraInfo);
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ public ColumnSchema getColumnSchema() {
+ return ColumnSchema.builder(this).build();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ColumnMetadata{");
+ sb.append("name='").append(name).append('\'');
+ sb.append(", type=").append(type);
+ sb.append(", ").append(nullable ? "nullable" : "nonnull");
+ if (comment != null) {
+ sb.append(", comment='").append(comment).append('\'');
+ }
+ if (extraInfo != null) {
+ sb.append(", extraInfo='").append(extraInfo).append('\'');
+ }
+ if (hidden) {
+ sb.append(", hidden");
+ }
+ if (!properties.isEmpty()) {
+ sb.append(", properties=").append(properties);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, nullable, comment, extraInfo, hidden);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ColumnMetadata other = (ColumnMetadata) obj;
+ return Objects.equals(this.name, other.name)
+ && Objects.equals(this.type, other.type)
+ && Objects.equals(this.nullable, other.nullable)
+ && Objects.equals(this.comment, other.comment)
+ && Objects.equals(this.extraInfo, other.extraInfo)
+ && Objects.equals(this.hidden, other.hidden);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builderFrom(ColumnMetadata columnMetadata) {
+ return new Builder(columnMetadata);
+ }
+
+ public static class Builder {
+ private String name;
+ private Type type;
+ private boolean nullable = true;
+ private Optional<String> comment = Optional.empty();
+ private Optional<String> extraInfo = Optional.empty();
+ private boolean hidden;
+ private Map<String, Object> properties = emptyMap();
+
+ private Builder() {}
+
+ private Builder(ColumnMetadata columnMetadata) {
+ this.name = columnMetadata.getName();
+ this.type = columnMetadata.getType();
+ this.nullable = columnMetadata.isNullable();
+ this.comment = columnMetadata.getComment();
+ this.extraInfo = columnMetadata.getExtraInfo();
+ this.hidden = columnMetadata.isHidden();
+ this.properties = columnMetadata.getProperties();
+ }
+
+ public Builder setName(String name) {
+ this.name = requireNonNull(name, "name is null");
+ return this;
+ }
+
+ public Builder setType(Type type) {
+ this.type = requireNonNull(type, "type is null");
+ return this;
+ }
+
+ public Builder setNullable(boolean nullable) {
+ this.nullable = nullable;
+ return this;
+ }
+
+ public Builder setComment(Optional<String> comment) {
+ this.comment = requireNonNull(comment, "comment is null");
+ return this;
+ }
+
+ public Builder setExtraInfo(Optional<String> extraInfo) {
+ this.extraInfo = requireNonNull(extraInfo, "extraInfo is null");
+ return this;
+ }
+
+ public Builder setHidden(boolean hidden) {
+ this.hidden = hidden;
+ return this;
+ }
+
+ public Builder setProperties(Map<String, Object> properties) {
+ this.properties = requireNonNull(properties, "properties is null");
+ return this;
+ }
+
+ public ColumnMetadata build() {
+ return new ColumnMetadata(
+ name, type, nullable, comment.orElse(null), extraInfo.orElse(null), hidden, properties);
+ }
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java
new file mode 100644
index 00000000000..a94ae08651a
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java
@@ -0,0 +1,121 @@
+/*
+ * 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.metadata;
+
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+
+public class ColumnSchema {
+ private final String name;
+ private final Type type;
+ private final boolean hidden;
+
+ private ColumnSchema(String name, Type type, boolean hidden) {
+ requireNonNull(type, "type is null");
+
+ this.name = name.toLowerCase(ENGLISH);
+ this.type = type;
+ this.hidden = hidden;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ColumnSchema that = (ColumnSchema) o;
+ return hidden == that.hidden && name.equals(that.name) && type.equals(that.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, hidden);
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", ColumnSchema.class.getSimpleName() + "[", "]")
+ .add("name='" + name + "'")
+ .add("type=" + type)
+ .add("hidden=" + hidden)
+ .toString();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(ColumnMetadata columnMetadata) {
+ return new Builder(columnMetadata);
+ }
+
+ public static class Builder {
+ private String name;
+ private Type type;
+ private boolean hidden;
+
+ private Builder() {}
+
+ private Builder(ColumnMetadata columnMetadata) {
+ this.name = columnMetadata.getName();
+ this.type = columnMetadata.getType();
+ this.hidden = columnMetadata.isHidden();
+ }
+
+ public Builder setName(String name) {
+ this.name = requireNonNull(name, "name is null");
+ return this;
+ }
+
+ public Builder setType(Type type) {
+ this.type = requireNonNull(type, "type is null");
+ return this;
+ }
+
+ public Builder setHidden(boolean hidden) {
+ this.hidden = hidden;
+ return this;
+ }
+
+ public ColumnSchema build() {
+ return new ColumnSchema(name, type, hidden);
+ }
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java
new file mode 100644
index 00000000000..f4880b58b6e
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java
@@ -0,0 +1,58 @@
+/*
+ * 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.metadata;
+
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+
+import java.util.Map;
+import java.util.Optional;
+
+public interface Metadata {
+
+ boolean tableExists(QualifiedObjectName name);
+
+ /**
+ * Return table schema definition for the specified table handle. Table schema definition is a set
+ * of information required by semantic analyzer to analyze the query.
+ *
+ * @throws RuntimeException if table handle is no longer valid
+ * @see #getTableMetadata(SessionInfo, TableHandle)
+ */
+ TableSchema getTableSchema(SessionInfo session, TableHandle tableHandle);
+
+ /**
+ * Return the metadata for the specified table handle.
+ *
+ * @throws RuntimeException if table handle is no longer valid
+ * @see #getTableSchema(SessionInfo, TableHandle) a different method which is less expensive.
+ */
+ TableMetadata getTableMetadata(SessionInfo session, TableHandle tableHandle);
+
+ /** Returns a table handle for the specified table name with a specified version */
+ Optional<TableHandle> getTableHandle(SessionInfo session, QualifiedObjectName name);
+
+ /**
+ * Gets all of the columns on the specified table, or an empty map if the columns cannot be
+ * enumerated.
+ *
+ * @throws RuntimeException if table handle is no longer valid
+ */
+ Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle);
+}
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 c134054fff1..3fea978e205 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
@@ -19,8 +19,11 @@
package org.apache.iotdb.db.queryengine.plan.relational.metadata;
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
import org.apache.iotdb.db.relational.sql.tree.Node;
import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
+import org.apache.iotdb.tsfile.read.common.type.Type;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -64,109 +67,51 @@ public class MetadataUtil {
}
public static QualifiedObjectName createQualifiedObjectName(
- Session session, Node node, QualifiedName name) {
+ SessionInfo session, Node node, QualifiedName name) {
requireNonNull(session, "session is null");
requireNonNull(name, "name is null");
if (name.getParts().size() > 3) {
- throw new TrinoException(SYNTAX_ERROR, format("Too many dots in table name: %s", name));
+ throw new SemanticException(String.format("Too many dots in table name: %s", name));
}
List<String> parts = Lists.reverse(name.getParts());
String objectName = parts.get(0);
- String schemaName =
- (parts.size() > 1)
- ? parts.get(1)
- : session
- .getSchema()
- .orElseThrow(
- () ->
- semanticException(
- MISSING_SCHEMA_NAME,
- node,
- "Schema must be specified when session schema is not set"));
- String catalogName =
+ String databaseName =
(parts.size() > 2)
? parts.get(2)
: session
- .getCatalog()
+ .getDatabaseName()
.orElseThrow(
() ->
- semanticException(
- MISSING_CATALOG_NAME,
- node,
+ new SemanticException(
"Catalog must be specified when session catalog is not set"));
- return new QualifiedObjectName(catalogName, schemaName, objectName);
+ return new QualifiedObjectName(databaseName, objectName);
}
- public static boolean tableExists(Metadata metadata, Session session, String table) {
- if (session.getCatalog().isEmpty() || session.getSchema().isEmpty()) {
+ public static boolean tableExists(Metadata metadata, SessionInfo session, String table) {
+ if (!session.getDatabaseName().isPresent()) {
return false;
}
- QualifiedObjectName name =
- new QualifiedObjectName(session.getCatalog().get(), session.getSchema().get(), table);
- return metadata.getTableHandle(session, name).isPresent();
- }
-
- public static void checkRoleExists(
- Session session,
- Node node,
- Metadata metadata,
- TrinoPrincipal principal,
- Optional<String> catalog) {
- if (principal.getType() == ROLE) {
- checkRoleExists(session, node, metadata, principal.getName(), catalog);
- }
- }
-
- public static void checkRoleExists(
- Session session, Node node, Metadata metadata, String role, Optional<String> catalog) {
- if (!metadata.roleExists(session, role, catalog)) {
- throw semanticException(
- ROLE_NOT_FOUND,
- node,
- "Role '%s' does not exist%s",
- role,
- catalog.map(c -> format(" in catalog '%s'", c)).orElse(""));
- }
- }
-
- public static Optional<String> processRoleCommandCatalog(
- Metadata metadata, Session session, Node node, Optional<String> catalog) {
- boolean legacyCatalogRoles = isLegacyCatalogRoles(session);
- // old role commands use only supported catalog roles and used session catalog as the default
- if (catalog.isEmpty() && legacyCatalogRoles) {
- catalog = session.getCatalog();
- if (catalog.isEmpty()) {
- throw semanticException(MISSING_CATALOG_NAME, node, "Session catalog must be set");
- }
- }
- catalog.ifPresent(
- catalogName -> getRequiredCatalogHandle(metadata, session, node, catalogName));
-
- if (catalog.isPresent() && !metadata.isCatalogManagedSecurity(session, catalog.get())) {
- throw semanticException(
- NOT_SUPPORTED, node, "Catalog '%s' does not support role management", catalog.get());
- }
-
- return catalog;
+ QualifiedObjectName name = new QualifiedObjectName(session.getDatabaseName().get(), table);
+ return metadata.tableExists(name);
}
public static class TableMetadataBuilder {
- public static TableMetadataBuilder tableMetadataBuilder(SchemaTableName tableName) {
+ public static TableMetadataBuilder tableMetadataBuilder(String tableName) {
return new TableMetadataBuilder(tableName);
}
- private final SchemaTableName tableName;
+ private final String tableName;
private final ImmutableList.Builder<ColumnMetadata> columns = ImmutableList.builder();
private final ImmutableMap.Builder<String, Object> properties = ImmutableMap.builder();
private final Optional<String> comment;
- private TableMetadataBuilder(SchemaTableName tableName) {
+ private TableMetadataBuilder(String tableName) {
this(tableName, Optional.empty());
}
- private TableMetadataBuilder(SchemaTableName tableName, Optional<String> comment) {
+ private TableMetadataBuilder(String tableName, Optional<String> comment) {
this.tableName = tableName;
this.comment = comment;
}
@@ -187,9 +132,8 @@ public class MetadataUtil {
return this;
}
- public ConnectorTableMetadata build() {
- return new ConnectorTableMetadata(
- tableName, columns.build(), properties.buildOrThrow(), comment);
+ public TableMetadata build() {
+ return new TableMetadata(tableName, columns.build(), properties.buildOrThrow(), comment);
}
}
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
similarity index 89%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
index 61d95d2e832..0f83a73b0d2 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
@@ -17,6 +17,6 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
-public class ExpressionAnalyzer {}
+public class TableHandle {}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java
new file mode 100644
index 00000000000..a3acb473d2c
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java
@@ -0,0 +1,93 @@
+/*
+ * 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.metadata;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Objects.requireNonNull;
+
+public class TableMetadata {
+ private final String table;
+ private final Optional<String> comment;
+ private final List<ColumnMetadata> columns;
+ private final Map<String, Object> properties;
+
+ public TableMetadata(String table, List<ColumnMetadata> columns) {
+ this(table, columns, emptyMap());
+ }
+
+ public TableMetadata(String table, List<ColumnMetadata> columns, Map<String, Object> properties) {
+ this(table, columns, properties, Optional.empty());
+ }
+
+ public TableMetadata(
+ String table,
+ List<ColumnMetadata> columns,
+ Map<String, Object> properties,
+ Optional<String> comment) {
+ requireNonNull(table, "table is null");
+ requireNonNull(columns, "columns is null");
+ requireNonNull(comment, "comment is null");
+
+ this.table = table;
+ this.columns = new ArrayList<>(columns);
+ this.properties = Collections.unmodifiableMap(new LinkedHashMap<>(properties));
+ this.comment = comment;
+ }
+
+ public String getTable() {
+ return table;
+ }
+
+ public List<ColumnMetadata> getColumns() {
+ return columns;
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ public Optional<String> getComment() {
+ return comment;
+ }
+
+ public TableSchema getTableSchema() {
+ return new TableSchema(
+ table, columns.stream().map(ColumnMetadata::getColumnSchema).collect(Collectors.toList()));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ConnectorTableMetadata{");
+ sb.append("table=").append(table);
+ sb.append(", columns=").append(columns);
+ sb.append(", properties=").append(properties);
+ comment.ifPresent(value -> sb.append(", comment='").append(value).append("'"));
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
similarity index 63%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
index 61d95d2e832..e3ed509931c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
@@ -17,6 +17,26 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
-public class ExpressionAnalyzer {}
+import java.util.List;
+
+public class TableSchema {
+
+ private final String tableName;
+
+ private final List<ColumnSchema> columns;
+
+ public TableSchema(String tableName, List<ColumnSchema> columns) {
+ this.tableName = tableName;
+ this.columns = columns;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public List<ColumnSchema> getColumns() {
+ return columns;
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java
new file mode 100644
index 00000000000..847197ba6b1
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java
@@ -0,0 +1,77 @@
+/*
+ * 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.planner;
+
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class Symbol implements Comparable<Symbol> {
+ private final String name;
+
+ public static Symbol from(Expression expression) {
+ checkArgument(expression instanceof SymbolReference, "Unexpected expression: %s", expression);
+ return new Symbol(((SymbolReference) expression).getName());
+ }
+
+ public Symbol(String name) {
+ requireNonNull(name, "name is null");
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SymbolReference toSymbolReference() {
+ return new SymbolReference(name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Symbol symbol = (Symbol) o;
+
+ return name.equals(symbol.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public int compareTo(Symbol o) {
+ return name.compareTo(o.name);
+ }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
similarity index 85%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
index 61d95d2e832..e8df34b2752 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
@@ -17,6 +17,8 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.planner;
-public class ExpressionAnalyzer {}
+public interface SymbolResolver {
+ Object getValue(Symbol symbol);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
similarity index 89%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
index 61d95d2e832..f61b67652d5 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
@@ -17,6 +17,6 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.security;
-public class ExpressionAnalyzer {}
+public interface AccessControl {}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
similarity index 78%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
index 61d95d2e832..fad9562a916 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
@@ -17,6 +17,16 @@
* under the License.
*/
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.security;
-public class ExpressionAnalyzer {}
+public class Identity {
+ private final String user;
+
+ public Identity(String user) {
+ this.user = user;
+ }
+
+ public String getUser() {
+ return user;
+ }
+}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
index d5780068d23..98e988c38bc 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
import org.apache.iotdb.tsfile.utils.Binary;
public class BinaryType implements Type {
- private static final BinaryType INSTANCE = new BinaryType();
+ private static final BinaryType TEXT = new BinaryType();
private BinaryType() {}
@@ -49,12 +49,22 @@ public class BinaryType implements Type {
return TypeEnum.BINARY;
}
+ @Override
+ public String getDisplayName() {
+ return "TEXT";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static BinaryType getInstance() {
- return INSTANCE;
+ return TEXT;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
index 3c278f4e6ee..fdb65f8d030 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
public class BooleanType implements Type {
- private static final BooleanType INSTANCE = new BooleanType();
+ public static final BooleanType BOOLEAN = new BooleanType();
private BooleanType() {}
@@ -49,12 +49,22 @@ public class BooleanType implements Type {
return TypeEnum.BOOLEAN;
}
+ @Override
+ public String getDisplayName() {
+ return "BOOLEAN";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static BooleanType getInstance() {
- return INSTANCE;
+ return BOOLEAN;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
index 8d074679052..0d64ffef1b7 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.DoubleColumnBuilder;
public class DoubleType implements Type {
- private static final DoubleType INSTANCE = new DoubleType();
+ public static final DoubleType DOUBLE = new DoubleType();
private DoubleType() {}
@@ -79,12 +79,22 @@ public class DoubleType implements Type {
return TypeEnum.DOUBLE;
}
+ @Override
+ public String getDisplayName() {
+ return "DOUBLE";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static DoubleType getInstance() {
- return INSTANCE;
+ return DOUBLE;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
index f1f619feb46..96d258f533b 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.FloatColumnBuilder;
public class FloatType implements Type {
- private static final FloatType INSTANCE = new FloatType();
+ private static final FloatType FLOAT = new FloatType();
private FloatType() {}
@@ -79,12 +79,22 @@ public class FloatType implements Type {
return TypeEnum.FLOAT;
}
+ @Override
+ public String getDisplayName() {
+ return "FLOAT";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static FloatType getInstance() {
- return INSTANCE;
+ return FLOAT;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
index 6c3e4dcc980..e0108f925d5 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.IntColumnBuilder;
public class IntType implements Type {
- private static final IntType INSTANCE = new IntType();
+ private static final IntType INT32 = new IntType();
private IntType() {}
@@ -79,12 +79,22 @@ public class IntType implements Type {
return TypeEnum.INT32;
}
+ @Override
+ public String getDisplayName() {
+ return "INT32";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static IntType getInstance() {
- return INSTANCE;
+ return INT32;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
index 39d451b1013..383bc37619f 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.LongColumnBuilder;
public class LongType implements Type {
- private static final LongType INSTANCE = new LongType();
+ private static final LongType INT64 = new LongType();
private LongType() {}
@@ -79,12 +79,22 @@ public class LongType implements Type {
return TypeEnum.INT64;
}
+ @Override
+ public String getDisplayName() {
+ return "INT64";
+ }
+
+ @Override
+ public boolean isComparable() {
+ return true;
+ }
+
@Override
public boolean isOrderable() {
return true;
}
public static LongType getInstance() {
- return INSTANCE;
+ return INT64;
}
}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
index a8383bb4158..1babec273aa 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
@@ -54,6 +54,7 @@ public interface Type {
default Binary getBinary(Column c, int position) {
throw new UnsupportedOperationException(getClass().getName());
}
+
/** Gets a Object at {@code position}. */
default Object getObject(Column c, int position) {
return c.getObject(position);
@@ -102,6 +103,12 @@ public interface Type {
TypeEnum getTypeEnum();
+ /** Returns the name of this type that should be displayed to end-users. */
+ String getDisplayName();
+
+ /** True if the type supports equalTo and hash. */
+ boolean isComparable();
+
/** True if the type supports compareTo. */
boolean isOrderable();
}