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 2c12cdf6c8f19aca13dffb3d14baa345c805d2c0 Author: JackieTien97 <[email protected]> AuthorDate: Wed Feb 28 20:59:37 2024 +0800 StatementAnalyzer --- .../java/org/apache/iotdb/rpc/TSStatusCode.java | 2 + .../db/queryengine/plan/analyze/TypeProvider.java | 92 +- .../relational/analyzer/ExpressionAnalysis.java | 34 +- .../relational/analyzer/ExpressionAnalyzer.java | 1531 +++++++++++++++++++- .../relational/analyzer/StatementAnalyzer.java | 3 + .../plan/relational/function/BoundSignature.java | 97 ++ .../plan/relational/function/FunctionId.java | 68 + .../plan/relational/function/FunctionKind.java} | 21 +- .../function/LongVariableConstraint.java | 68 + .../plan/relational/function/OperatorType.java | 67 + .../plan/relational/function/Signature.java | 220 +++ .../function/TypeVariableConstraint.java | 200 +++ .../plan/relational/metadata/Metadata.java | 6 + .../metadata/OperatorNotFoundException.java | 119 ++ .../plan/relational/metadata/ResolvedFunction.java | 107 ++ .../plan/relational/type/NamedTypeSignature.java | 79 + .../plan/relational/type/ParameterKind.java} | 21 +- .../plan/relational/type/RowFieldName.java | 63 + .../plan/relational/type/StandardTypes.java | 55 + .../plan/relational/type/TypeSignature.java | 207 +++ .../relational/type/TypeSignatureParameter.java | 175 +++ .../relational/sql/tree/StackableAstVisitor.java | 65 + .../iotdb/tsfile/read/common/type/RowType.java | 21 + .../iotdb/tsfile/read/common/type/TypeEnum.java | 4 +- .../iotdb/tsfile/read/common/type/TypeFactory.java | 22 + .../iotdb/tsfile/read/common/type/UnknownType.java | 87 ++ 26 files changed, 3356 insertions(+), 78 deletions(-) diff --git a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java index b8407bb86de..1056e470a09 100644 --- a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java +++ b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java @@ -113,6 +113,8 @@ public enum TSStatusCode { NO_SUCH_QUERY(714), QUERY_WAS_KILLED(715), + OPERATOR_NOT_FOUND(716), + // Authentication INIT_AUTH_ERROR(800), WRONG_LOGIN_PASSWORD(801), diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java index 0c3bcee6466..d12f71f5303 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java @@ -19,16 +19,25 @@ package org.apache.iotdb.db.queryengine.plan.analyze; +import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol; import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType; +import org.apache.iotdb.tsfile.read.common.type.Type; +import org.apache.iotdb.tsfile.read.common.type.TypeEnum; +import org.apache.iotdb.tsfile.read.common.type.TypeFactory; import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils; +import com.google.common.collect.ImmutableMap; + import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; import static org.apache.iotdb.db.queryengine.plan.expression.leaf.TimestampOperand.TIMESTAMP_EXPRESSION_STRING; public class TypeProvider { @@ -39,6 +48,7 @@ public class TypeProvider { public TypeProvider() { this.typeMap = new HashMap<>(); + types = null; } public TypeProvider(Map<String, TSDataType> typeMap, TemplatedInfo templatedInfo) { @@ -46,6 +56,7 @@ public class TypeProvider { this.templatedInfo = templatedInfo; // The type of TimeStampOperand is INT64 this.typeMap.putIfAbsent(TIMESTAMP_EXPRESSION_STRING, TSDataType.INT64); + types = null; } public TSDataType getType(String symbol) { @@ -71,6 +82,50 @@ public class TypeProvider { return this.templatedInfo; } + // ----------------------used for relational model---------------------------- + + private final Map<Symbol, Type> types; + + public static TypeProvider viewOf(Map<Symbol, Type> types) { + return new TypeProvider(types); + } + + public static TypeProvider copyOf(Map<Symbol, Type> types) { + return new TypeProvider(ImmutableMap.copyOf(types)); + } + + public static TypeProvider empty() { + return new TypeProvider(ImmutableMap.of()); + } + + private TypeProvider(Map<Symbol, Type> types) { + this.types = types; + + this.typeMap = null; + } + + private TypeProvider( + Map<String, TSDataType> typeMap, TemplatedInfo templatedInfo, Map<Symbol, Type> types) { + this.typeMap = typeMap; + this.templatedInfo = templatedInfo; + this.types = types; + } + + public Type get(Symbol symbol) { + requireNonNull(symbol, "symbol is null"); + + Type type = types.get(symbol); + checkArgument(type != null, "no type found for symbol '%s'", symbol); + + return type; + } + + public Map<Symbol, Type> allTypes() { + // types may be a HashMap, so creating an ImmutableMap here would add extra cost when allTypes + // gets called frequently + return Collections.unmodifiableMap(types); + } + public void serialize(ByteBuffer byteBuffer) { ReadWriteIOUtils.write(typeMap.size(), byteBuffer); for (Map.Entry<String, TSDataType> entry : typeMap.entrySet()) { @@ -84,6 +139,16 @@ public class TypeProvider { ReadWriteIOUtils.write((byte) 1, byteBuffer); templatedInfo.serialize(byteBuffer); } + + if (types == null) { + ReadWriteIOUtils.write((byte) 0, byteBuffer); + } else { + ReadWriteIOUtils.write(types.size(), byteBuffer); + for (Map.Entry<Symbol, Type> entry : types.entrySet()) { + ReadWriteIOUtils.write(entry.getKey().getName(), byteBuffer); + ReadWriteIOUtils.write(entry.getValue().getTypeEnum().ordinal(), byteBuffer); + } + } } public void serialize(DataOutputStream stream) throws IOException { @@ -99,11 +164,21 @@ public class TypeProvider { ReadWriteIOUtils.write((byte) 1, stream); templatedInfo.serialize(stream); } + + if (types == null) { + ReadWriteIOUtils.write((byte) 0, stream); + } else { + ReadWriteIOUtils.write(types.size(), stream); + for (Map.Entry<Symbol, Type> entry : types.entrySet()) { + ReadWriteIOUtils.write(entry.getKey().getName(), stream); + ReadWriteIOUtils.write(entry.getValue().getTypeEnum().ordinal(), stream); + } + } } public static TypeProvider deserialize(ByteBuffer byteBuffer) { int mapSize = ReadWriteIOUtils.readInt(byteBuffer); - Map<String, TSDataType> typeMap = new HashMap<>(); + Map<String, TSDataType> typeMap = new HashMap<>(mapSize); while (mapSize > 0) { typeMap.put( ReadWriteIOUtils.readString(byteBuffer), @@ -117,7 +192,20 @@ public class TypeProvider { templatedInfo = TemplatedInfo.deserialize(byteBuffer); } - return new TypeProvider(typeMap, templatedInfo); + Map<Symbol, Type> types = null; + byte hasTypes = ReadWriteIOUtils.readByte(byteBuffer); + if (hasTypes == 1) { + mapSize = ReadWriteIOUtils.readInt(byteBuffer); + types = new HashMap<>(mapSize); + while (mapSize > 0) { + types.put( + new Symbol(ReadWriteIOUtils.readString(byteBuffer)), + TypeFactory.getType(TypeEnum.values()[ReadWriteIOUtils.readInt(byteBuffer)])); + mapSize--; + } + } + + return new TypeProvider(typeMap, templatedInfo, types); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java index 51315c5c889..a576308bce5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java @@ -21,7 +21,6 @@ package org.apache.iotdb.db.queryengine.plan.relational.analyzer; 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.FunctionCall; import org.apache.iotdb.db.relational.sql.tree.InPredicate; import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression; import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression; @@ -37,31 +36,28 @@ import static java.util.Objects.requireNonNull; public class ExpressionAnalysis { private final Map<NodeRef<Expression>, Type> expressionTypes; - private final Map<NodeRef<Expression>, Type> expressionCoercions; - private final Set<NodeRef<Expression>> typeOnlyCoercions; + // private final Map<NodeRef<Expression>, Type> expressionCoercions; + // private final Set<NodeRef<Expression>> typeOnlyCoercions; private final Map<NodeRef<Expression>, ResolvedField> columnReferences; private final Set<NodeRef<InPredicate>> subqueryInPredicates; private final Set<NodeRef<SubqueryExpression>> subqueries; private final Set<NodeRef<ExistsPredicate>> existsSubqueries; private final Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons; - private final Set<NodeRef<FunctionCall>> windowFunctions; public ExpressionAnalysis( Map<NodeRef<Expression>, Type> expressionTypes, - Map<NodeRef<Expression>, Type> expressionCoercions, Set<NodeRef<InPredicate>> subqueryInPredicates, Set<NodeRef<SubqueryExpression>> subqueries, Set<NodeRef<ExistsPredicate>> existsSubqueries, Map<NodeRef<Expression>, ResolvedField> columnReferences, - Set<NodeRef<Expression>> typeOnlyCoercions, - Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons, - Set<NodeRef<FunctionCall>> windowFunctions) { + Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons) { this.expressionTypes = ImmutableMap.copyOf(requireNonNull(expressionTypes, "expressionTypes is null")); - this.expressionCoercions = - ImmutableMap.copyOf(requireNonNull(expressionCoercions, "expressionCoercions is null")); - this.typeOnlyCoercions = - ImmutableSet.copyOf(requireNonNull(typeOnlyCoercions, "typeOnlyCoercions is null")); + // this.expressionCoercions = + // ImmutableMap.copyOf(requireNonNull(expressionCoercions, "expressionCoercions is + // null")); + // this.typeOnlyCoercions = + // ImmutableSet.copyOf(requireNonNull(typeOnlyCoercions, "typeOnlyCoercions is null")); this.columnReferences = ImmutableMap.copyOf(requireNonNull(columnReferences, "columnReferences is null")); this.subqueryInPredicates = @@ -71,8 +67,6 @@ public class ExpressionAnalysis { ImmutableSet.copyOf(requireNonNull(existsSubqueries, "existsSubqueries is null")); this.quantifiedComparisons = ImmutableSet.copyOf(requireNonNull(quantifiedComparisons, "quantifiedComparisons is null")); - this.windowFunctions = - ImmutableSet.copyOf(requireNonNull(windowFunctions, "windowFunctions is null")); } public Type getType(Expression expression) { @@ -83,14 +77,6 @@ public class ExpressionAnalysis { return expressionTypes; } - public Type getCoercion(Expression expression) { - return expressionCoercions.get(NodeRef.of(expression)); - } - - public boolean isTypeOnlyCoercion(Expression expression) { - return typeOnlyCoercions.contains(NodeRef.of(expression)); - } - public boolean isColumnReference(Expression node) { return columnReferences.containsKey(NodeRef.of(node)); } @@ -110,8 +96,4 @@ public class ExpressionAnalysis { public Set<NodeRef<QuantifiedComparisonExpression>> getQuantifiedComparisons() { return quantifiedComparisons; } - - public Set<NodeRef<FunctionCall>> getWindowFunctions() { - return windowFunctions; - } } 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 bd906b816f1..9454fa1d1ed 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,19 +19,1406 @@ 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.WarningCollector; +import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider; +import org.apache.iotdb.db.queryengine.plan.relational.function.BoundSignature; +import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.OperatorNotFoundException; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName; +import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol; import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl; +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.BetweenPredicate; +import org.apache.iotdb.db.relational.sql.tree.BinaryLiteral; +import org.apache.iotdb.db.relational.sql.tree.BooleanLiteral; +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.CurrentDatabase; +import org.apache.iotdb.db.relational.sql.tree.CurrentTime; +import org.apache.iotdb.db.relational.sql.tree.CurrentUser; +import org.apache.iotdb.db.relational.sql.tree.DecimalLiteral; +import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression; +import org.apache.iotdb.db.relational.sql.tree.DoubleLiteral; +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.GenericLiteral; 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.LogicalExpression; +import org.apache.iotdb.db.relational.sql.tree.LongLiteral; +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.NullLiteral; +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.Row; +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.StackableAstVisitor; +import org.apache.iotdb.db.relational.sql.tree.StringLiteral; +import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression; +import org.apache.iotdb.db.relational.sql.tree.SymbolReference; +import org.apache.iotdb.db.relational.sql.tree.Trim; +import org.apache.iotdb.db.relational.sql.tree.WhenClause; +import org.apache.iotdb.tsfile.read.common.type.BinaryType; +import org.apache.iotdb.tsfile.read.common.type.RowType; +import org.apache.iotdb.tsfile.read.common.type.Type; +import org.apache.iotdb.tsfile.read.common.type.UnknownType; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; + +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.lang.String.format; +import static java.util.Collections.unmodifiableMap; +import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; +import static org.apache.iotdb.db.relational.sql.tree.DereferenceExpression.isQualifiedAllFieldsReference; +import static org.apache.iotdb.tsfile.read.common.type.BinaryType.TEXT; +import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN; +import static org.apache.iotdb.tsfile.read.common.type.DoubleType.DOUBLE; +import static org.apache.iotdb.tsfile.read.common.type.FloatType.FLOAT; +import static org.apache.iotdb.tsfile.read.common.type.IntType.INT32; +import static org.apache.iotdb.tsfile.read.common.type.LongType.INT64; +import static org.apache.iotdb.tsfile.read.common.type.UnknownType.UNKNOWN; public class ExpressionAnalyzer { + private final Metadata metadata; + private final AccessControl accessControl; + + private final BiFunction<Node, CorrelationSupport, StatementAnalyzer> statementAnalyzerFactory; + + private final TypeProvider symbolTypes; + + private final Set<NodeRef<SubqueryExpression>> subqueries = new LinkedHashSet<>(); + private final Set<NodeRef<ExistsPredicate>> existsSubqueries = new LinkedHashSet<>(); + + private final Set<NodeRef<InPredicate>> subqueryInPredicates = new LinkedHashSet<>(); + + private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>(); + private final Map<NodeRef<Expression>, Type> expressionTypes = new LinkedHashMap<>(); + private final Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons = + new LinkedHashSet<>(); + + private final Multimap<QualifiedObjectName, String> tableColumnReferences = HashMultimap.create(); + + // Track referenced fields from source relation node + private final Multimap<NodeRef<Node>, Field> referencedFields = HashMultimap.create(); + + private final SessionInfo session; + + private final Map<NodeRef<Parameter>, Expression> parameters; + private final WarningCollector warningCollector; + + private final Function<Expression, Type> getPreanalyzedType; + + private final List<Field> sourceFields = new ArrayList<>(); + + private ExpressionAnalyzer( + Metadata metadata, + AccessControl accessControl, + Analysis analysis, + SessionInfo session, + TypeProvider types, + WarningCollector warningCollector) { + this( + metadata, + accessControl, + (node, correlationSupport) -> + new StatementAnalyzer( + analysis, accessControl, warningCollector, session, metadata, correlationSupport), + session, + types, + analysis.getParameters(), + warningCollector, + analysis::getType); + } + + ExpressionAnalyzer( + Metadata metadata, + AccessControl accessControl, + BiFunction<Node, CorrelationSupport, StatementAnalyzer> statementAnalyzerFactory, + SessionInfo session, + TypeProvider symbolTypes, + Map<NodeRef<Parameter>, Expression> parameters, + WarningCollector warningCollector, + Function<Expression, Type> getPreanalyzedType) { + this.metadata = requireNonNull(metadata, "metadata is null"); + this.accessControl = requireNonNull(accessControl, "accessControl is null"); + this.statementAnalyzerFactory = + requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null"); + this.session = requireNonNull(session, "session is null"); + this.symbolTypes = requireNonNull(symbolTypes, "symbolTypes is null"); + this.parameters = requireNonNull(parameters, "parameters is null"); + this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); + this.getPreanalyzedType = requireNonNull(getPreanalyzedType, "getPreanalyzedType is null"); + } + + public Map<NodeRef<Expression>, Type> getExpressionTypes() { + return unmodifiableMap(expressionTypes); + } + + public Type setExpressionType(Expression expression, Type type) { + requireNonNull(expression, "expression cannot be null"); + requireNonNull(type, "type cannot be null"); + + expressionTypes.put(NodeRef.of(expression), type); + + return type; + } + + private Type getExpressionType(Expression expression) { + requireNonNull(expression, "expression cannot be null"); + + Type type = expressionTypes.get(NodeRef.of(expression)); + checkState(type != null, "Expression not yet analyzed: %s", expression); + return type; + } + + public Set<NodeRef<InPredicate>> getSubqueryInPredicates() { + return unmodifiableSet(subqueryInPredicates); + } + + public Map<NodeRef<Expression>, ResolvedField> getColumnReferences() { + return unmodifiableMap(columnReferences); + } + + public Type analyze(Expression expression, Scope scope) { + Visitor visitor = new Visitor(scope, warningCollector); + return visitor.process( + expression, + new StackableAstVisitor.StackableAstVisitorContext<>( + Context.notInLambda(scope, CorrelationSupport.ALLOWED))); + } + + public Type analyze(Expression expression, Scope scope, CorrelationSupport correlationSupport) { + Visitor visitor = new Visitor(scope, warningCollector); + return visitor.process( + expression, + new StackableAstVisitor.StackableAstVisitorContext<>( + Context.notInLambda(scope, correlationSupport))); + } + + private Type analyze(Expression expression, Scope scope, Set<String> labels) { + Visitor visitor = new Visitor(scope, warningCollector); + return visitor.process( + expression, + new StackableAstVisitor.StackableAstVisitorContext<>( + Context.patternRecognition(scope, labels))); + } + + private Type analyze(Expression expression, Scope baseScope, Context context) { + Visitor visitor = new Visitor(baseScope, warningCollector); + return visitor.process( + expression, new StackableAstVisitor.StackableAstVisitorContext<>(context)); + } + + public Set<NodeRef<SubqueryExpression>> getSubqueries() { + return unmodifiableSet(subqueries); + } + + public Set<NodeRef<ExistsPredicate>> getExistsSubqueries() { + return unmodifiableSet(existsSubqueries); + } + + public Set<NodeRef<QuantifiedComparisonExpression>> getQuantifiedComparisons() { + return unmodifiableSet(quantifiedComparisons); + } + + public Multimap<QualifiedObjectName, String> getTableColumnReferences() { + return tableColumnReferences; + } + + public List<Field> getSourceFields() { + return sourceFields; + } + + private class Visitor extends StackableAstVisitor<Type, Context> { + // Used to resolve FieldReferences (e.g. during local execution planning) + private final Scope baseScope; + private final WarningCollector warningCollector; + + public Visitor(Scope baseScope, WarningCollector warningCollector) { + this.baseScope = requireNonNull(baseScope, "baseScope is null"); + this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); + } + + @Override + public Type process(Node node, @Nullable StackableAstVisitorContext<Context> context) { + if (node instanceof Expression) { + // don't double process a node + Type type = expressionTypes.get(NodeRef.of(((Expression) node))); + if (type != null) { + return type; + } + } + return super.process(node, context); + } + + @Override + protected Type visitRow(Row node, StackableAstVisitorContext<Context> context) { + List<Type> types = + node.getItems().stream().map(child -> process(child, context)).collect(toImmutableList()); + + Type type = RowType.anonymous(types); + return setExpressionType(node, type); + } + + @Override + protected Type visitCurrentTime(CurrentTime node, StackableAstVisitorContext<Context> context) { + if (requireNonNull(node.getFunction()) == CurrentTime.Function.TIMESTAMP) { + return setExpressionType(node, INT64); + } + throw new UnsupportedOperationException(node.toString()); + } + + @Override + protected Type visitSymbolReference( + SymbolReference node, StackableAstVisitorContext<Context> context) { + // if (context.getContext().isInLambda()) { + // Optional<ResolvedField> resolvedField = + // context.getContext().getScope().tryResolveField(node, + // QualifiedName.of(node.getName())); + // if (resolvedField.isPresent() && + // + // context.getContext().getFieldToLambdaArgumentDeclaration().containsKey(FieldId.from(resolvedField.get()))) { + // return setExpressionType(node, resolvedField.get().getType()); + // } + // } + Type type = symbolTypes.get(Symbol.from(node)); + return setExpressionType(node, type); + } + + @Override + protected Type visitIdentifier(Identifier node, StackableAstVisitorContext<Context> context) { + ResolvedField resolvedField = + context.getContext().getScope().resolveField(node, QualifiedName.of(node.getValue())); + return handleResolvedField(node, resolvedField, context); + } + + private Type handleResolvedField( + Expression node, ResolvedField resolvedField, StackableAstVisitorContext<Context> context) { + if (!resolvedField.isLocal() + && context.getContext().getCorrelationSupport() != CorrelationSupport.ALLOWED) { + throw new SemanticException( + String.format( + "Reference to column '%s' from outer scope not allowed in this context", node)); + } + + FieldId fieldId = FieldId.from(resolvedField); + Field field = resolvedField.getField(); + // if (context.getContext().isInLambda()) { + // LambdaArgumentDeclaration lambdaArgumentDeclaration = + // context.getContext().getFieldToLambdaArgumentDeclaration().get(fieldId); + // if (lambdaArgumentDeclaration != null) { + // // Lambda argument reference is not a column reference + // lambdaArgumentReferences.put(NodeRef.of((Identifier) node), + // lambdaArgumentDeclaration); + // return setExpressionType(node, field.getType()); + // } + // } + + if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) { + tableColumnReferences.put(field.getOriginTable().get(), field.getOriginColumnName().get()); + } + + sourceFields.add(field); + + fieldId + .getRelationId() + .getSourceNode() + .ifPresent(source -> referencedFields.put(NodeRef.of(source), field)); + + ResolvedField previous = columnReferences.put(NodeRef.of(node), resolvedField); + checkState(previous == null, "%s already known to refer to %s", node, previous); + + return setExpressionType(node, field.getType()); + } + + @Override + protected Type visitDereferenceExpression( + DereferenceExpression node, StackableAstVisitorContext<Context> context) { + if (isQualifiedAllFieldsReference(node)) { + throw new SemanticException("<identifier>.* not allowed in this context"); + } + + QualifiedName qualifiedName = DereferenceExpression.getQualifiedName(node); + + // If this Dereference looks like column reference, try match it to column first. + if (qualifiedName != null) { + Scope scope = context.getContext().getScope(); + Optional<ResolvedField> resolvedField = scope.tryResolveField(node, qualifiedName); + if (resolvedField.isPresent()) { + return handleResolvedField(node, resolvedField.get(), context); + } + if (!scope.isColumnReference(qualifiedName)) { + throw new SemanticException( + String.format("Column '%s' cannot be resolved", qualifiedName)); + } + } + + Type baseType = process(node.getBase(), context); + if (!(baseType instanceof RowType)) { + throw new SemanticException( + String.format("Expression %s is not of type ROW", node.getBase())); + } + + RowType rowType = (RowType) baseType; + + Identifier field = + node.getField().orElseThrow(() -> new NoSuchElementException("No value present")); + String fieldName = field.getValue(); + + boolean foundFieldName = false; + Type rowFieldType = null; + for (RowType.Field rowField : rowType.getFields()) { + if (fieldName.equalsIgnoreCase(rowField.getName().orElse(null))) { + if (foundFieldName) { + throw new SemanticException( + String.format("Ambiguous row field reference: %s", fieldName)); + } + foundFieldName = true; + rowFieldType = rowField.getType(); + } + } + + if (rowFieldType == null) { + throw new SemanticException(String.format("Column '%s' cannot be resolved", qualifiedName)); + } + + return setExpressionType(node, rowFieldType); + } + + @Override + protected Type visitNotExpression( + NotExpression node, StackableAstVisitorContext<Context> context) { + coerceType(context, node.getValue(), BOOLEAN, "Value of logical NOT expression"); + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitLogicalExpression( + LogicalExpression node, StackableAstVisitorContext<Context> context) { + for (Expression term : node.getTerms()) { + coerceType(context, term, BOOLEAN, "Logical expression term"); + } + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitComparisonExpression( + ComparisonExpression node, StackableAstVisitorContext<Context> context) { + OperatorType operatorType = null; + switch (node.getOperator()) { + case EQUAL: + case NOT_EQUAL: + operatorType = OperatorType.EQUAL; + break; + case LESS_THAN: + case GREATER_THAN: + operatorType = OperatorType.LESS_THAN; + break; + case LESS_THAN_OR_EQUAL: + case GREATER_THAN_OR_EQUAL: + operatorType = OperatorType.LESS_THAN_OR_EQUAL; + break; + case IS_DISTINCT_FROM: + operatorType = OperatorType.IS_DISTINCT_FROM; + break; + } + + return getOperator(context, node, operatorType, node.getLeft(), node.getRight()); + } + + @Override + protected Type visitIsNullPredicate( + IsNullPredicate node, StackableAstVisitorContext<Context> context) { + process(node.getValue(), context); + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitIsNotNullPredicate( + IsNotNullPredicate node, StackableAstVisitorContext<Context> context) { + process(node.getValue(), context); + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitNullIfExpression( + NullIfExpression node, StackableAstVisitorContext<Context> context) { + Type firstType = process(node.getFirst(), context); + Type secondType = process(node.getSecond(), context); + + if (!firstType.equals(secondType)) { + throw new SemanticException( + String.format("Types are not comparable with NULLIF: %s vs %s", firstType, secondType)); + } + + return setExpressionType(node, firstType); + } + + @Override + protected Type visitIfExpression( + IfExpression node, StackableAstVisitorContext<Context> context) { + coerceType(context, node.getCondition(), BOOLEAN, "IF condition"); + + Type type; + if (node.getFalseValue().isPresent()) { + type = + coerceToSingleType( + context, + node, + "Result types for IF must be the same", + node.getTrueValue(), + node.getFalseValue().get()); + } else { + type = process(node.getTrueValue(), context); + } + + return setExpressionType(node, type); + } + + @Override + protected Type visitSearchedCaseExpression( + SearchedCaseExpression node, StackableAstVisitorContext<Context> context) { + for (WhenClause whenClause : node.getWhenClauses()) { + coerceType(context, whenClause.getOperand(), BOOLEAN, "CASE WHEN clause"); + } + + Type type = + coerceToSingleType( + context, + "All CASE results", + getCaseResultExpressions(node.getWhenClauses(), node.getDefaultValue())); + setExpressionType(node, type); + + for (WhenClause whenClause : node.getWhenClauses()) { + Type whenClauseType = process(whenClause.getResult(), context); + setExpressionType(whenClause, whenClauseType); + } + + return type; + } + + @Override + protected Type visitSimpleCaseExpression( + SimpleCaseExpression node, StackableAstVisitorContext<Context> context) { + coerceCaseOperandToToSingleType(node, context); + + Type type = + coerceToSingleType( + context, + "All CASE results", + getCaseResultExpressions(node.getWhenClauses(), node.getDefaultValue())); + setExpressionType(node, type); + + for (WhenClause whenClause : node.getWhenClauses()) { + Type whenClauseType = process(whenClause.getResult(), context); + setExpressionType(whenClause, whenClauseType); + } + + return type; + } + + private void coerceCaseOperandToToSingleType( + SimpleCaseExpression node, StackableAstVisitorContext<Context> context) { + Type operandType = process(node.getOperand(), context); + + List<WhenClause> whenClauses = node.getWhenClauses(); + List<Type> whenOperandTypes = new ArrayList<>(whenClauses.size()); + + for (WhenClause whenClause : whenClauses) { + Expression whenOperand = whenClause.getOperand(); + Type whenOperandType = process(whenOperand, context); + whenOperandTypes.add(whenOperandType); + + if (!operandType.equals(whenOperandType)) { + throw new SemanticException( + String.format( + "CASE operand type does not match WHEN clause operand type: %s vs %s", + operandType, whenOperandType)); + } + } + + for (int i = 0; i < whenOperandTypes.size(); i++) { + Type whenOperandType = whenOperandTypes.get(i); + if (!whenOperandType.equals(operandType)) { + // Expression whenOperand = whenClauses.get(i).getOperand(); + throw new SemanticException( + String.format( + "CASE operand type does not match WHEN clause operand type: %s vs %s", + operandType, whenOperandType)); + // addOrReplaceExpressionCoercion(whenOperand, whenOperandType, operandType); + } + } + } + + private List<Expression> getCaseResultExpressions( + List<WhenClause> whenClauses, Optional<Expression> defaultValue) { + List<Expression> resultExpressions = new ArrayList<>(); + for (WhenClause whenClause : whenClauses) { + resultExpressions.add(whenClause.getResult()); + } + defaultValue.ifPresent(resultExpressions::add); + return resultExpressions; + } + + @Override + protected Type visitCoalesceExpression( + CoalesceExpression node, StackableAstVisitorContext<Context> context) { + Type type = coerceToSingleType(context, "All COALESCE operands", node.getOperands()); + + return setExpressionType(node, type); + } + + @Override + protected Type visitArithmeticUnary( + ArithmeticUnaryExpression node, StackableAstVisitorContext<Context> context) { + switch (node.getSign()) { + case PLUS: + Type type = process(node.getValue(), context); + + if (!type.equals(DOUBLE) + && !type.equals(FLOAT) + && !type.equals(INT32) + && !type.equals(INT64)) { + // TODO: figure out a type-agnostic way of dealing with this. Maybe add a special unary + // operator + // that types can chose to implement, or piggyback on the existence of the negation + // operator + throw new SemanticException( + String.format("Unary '+' operator cannot by applied to %s type", type)); + } + return setExpressionType(node, type); + case MINUS: + return getOperator(context, node, OperatorType.NEGATION, node.getValue()); + default: + throw new IllegalArgumentException("Unknown sign: " + node.getSign()); + } + } + + @Override + protected Type visitArithmeticBinary( + ArithmeticBinaryExpression node, StackableAstVisitorContext<Context> context) { + return getOperator( + context, + node, + OperatorType.valueOf(node.getOperator().name()), + node.getLeft(), + node.getRight()); + } + + @Override + protected Type visitLikePredicate( + LikePredicate node, StackableAstVisitorContext<Context> context) { + Type valueType = process(node.getValue(), context); + if (!(valueType instanceof BinaryType)) { + throw new SemanticException( + String.format( + "Left side of LIKE expression must evaluate to a BinaryType (actual: %s)", + valueType)); + } + + Type patternType = process(node.getPattern(), context); + if (!(patternType instanceof BinaryType)) { + throw new SemanticException( + String.format( + "Pattern for LIKE expression must evaluate to a BinaryType (actual: %s)", + patternType)); + } + if (node.getEscape().isPresent()) { + Expression escape = node.getEscape().get(); + Type escapeType = process(escape, context); + if (!(escapeType instanceof BinaryType)) { + throw new SemanticException( + String.format( + "Escape for LIKE expression must evaluate to a BinaryType (actual: %s)", + escapeType)); + } + } + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitStringLiteral( + StringLiteral node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, TEXT); + } + + @Override + protected Type visitBinaryLiteral( + BinaryLiteral node, StackableAstVisitorContext<Context> context) { + throw new SemanticException("BinaryLiteral is not supported yet."); + } + + @Override + protected Type visitLongLiteral(LongLiteral node, StackableAstVisitorContext<Context> context) { + if (node.getParsedValue() >= Integer.MIN_VALUE + && node.getParsedValue() <= Integer.MAX_VALUE) { + return setExpressionType(node, INT32); + } + + return setExpressionType(node, INT64); + } + + @Override + protected Type visitDoubleLiteral( + DoubleLiteral node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, DOUBLE); + } + + @Override + protected Type visitDecimalLiteral( + DecimalLiteral node, StackableAstVisitorContext<Context> context) { + throw new SemanticException("DecimalLiteral is not supported yet."); + } + + @Override + protected Type visitBooleanLiteral( + BooleanLiteral node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitGenericLiteral( + GenericLiteral node, StackableAstVisitorContext<Context> context) { + throw new SemanticException("GenericLiteral is not supported yet."); + } + + @Override + protected Type visitNullLiteral(NullLiteral node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, UNKNOWN); + } + + @Override + protected Type visitFunctionCall( + FunctionCall node, StackableAstVisitorContext<Context> context) { + // TODO implememt function call + throw new SemanticException("FunctionCall is not implemented yet."); + // boolean isAggregation = functionResolver.isAggregationFunction(session, + // node.getName(), accessControl); + // // argument of the form `label.*` is only allowed for row pattern count function + // node.getArguments().stream() + // .filter(DereferenceExpression::isQualifiedAllFieldsReference) + // .findAny() + // .ifPresent(allRowsReference -> { + // if (node.getArguments().size() > 1) { + // throw new SemanticException( + // "label.* syntax is only supported as the only argument of row pattern + // count function"); + // } + // }); + // + // if (node.isDistinct() && !isAggregation) { + // throw new SemanticException("DISTINCT is not supported for non-aggregation + // functions"); + // } + // + // List<TypeSignatureProvider> argumentTypes = getCallArgumentTypes(node.getArguments(), + // context); + // + // ResolvedFunction function; + // try { + // function = functionResolver.resolveFunction(session, node.getName(), argumentTypes, + // accessControl); + // } catch (TrinoException e) { + // if (e.getLocation().isPresent()) { + // // If analysis of any of the argument types (which is done lazily to deal with + // lambda + // // expressions) fails, we want to report the original reason for the failure + // throw e; + // } + // + // // otherwise, it must have failed due to a missing function or other reason, so we + // report an error at the + // // current location + // + // throw new TrinoException(e::getErrorCode, extractLocation(node), e.getMessage(), e); + // } + // + // if (node.getArguments().size() > 127) { + // throw new SemanticException(String.format("Too many arguments for function call + // %s()", + // function.getSignature().getName().getFunctionName())); + // } + // + // BoundSignature signature = function.getSignature(); + // for (int i = 0; i < argumentTypes.size(); i++) { + // Expression expression = node.getArguments().get(i); + // Type expectedType = signature.getArgumentTypes().get(i); + // if (expectedType == null) { + // throw new NullPointerException(format("Type '%s' not found", + // signature.getArgumentTypes().get(i))); + // } + // if (node.isDistinct() && !expectedType.isComparable()) { + // throw new SemanticException(String.format("DISTINCT can only be applied to + // comparable types (actual: %s)", + // expectedType)); + // } + // Type actualType = + // plannerContext.getTypeManager().getType(argumentTypes.get(i).getTypeSignature()); + // + // coerceType(expression, actualType, expectedType, String.format("Function %s argument + // %d", function, i)); + // } + // resolvedFunctions.put(NodeRef.of(node), function); + // + // Type type = signature.getReturnType(); + // return setExpressionType(node, type); + } + + // public List<TypeSignatureProvider> getCallArgumentTypes(List<Expression> arguments, + // + // StackableAstVisitorContext<Context> context) { + // ImmutableList.Builder<TypeSignatureProvider> argumentTypesBuilder = + // ImmutableList.builder(); + // for (Expression argument : arguments) { + // if (isQualifiedAllFieldsReference(argument)) { + // // to resolve `count(label.*)` correctly, we should skip the argument, like for + // `count(*)` + // // process the argument but do not include it in the list + // DereferenceExpression allRowsDereference = (DereferenceExpression) argument; + // String label = label((Identifier) allRowsDereference.getBase()); + // if (!context.getContext().getLabels().contains(label)) { + // throw semanticException(INVALID_FUNCTION_ARGUMENT, allRowsDereference.getBase(), + // "%s is not a primary pattern variable or subset name", label); + // } + // labelDereferences.put(NodeRef.of(allRowsDereference), new + // LabelPrefixedReference(label)); + // } else { + // argumentTypesBuilder.add(new TypeSignatureProvider(process(argument, + // context).getTypeSignature())); + // } + // } + // + // return argumentTypesBuilder.build(); + // } + + private String label(Identifier identifier) { + return identifier.getCanonicalValue(); + } + + @Override + protected Type visitCurrentDatabase( + CurrentDatabase node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, TEXT); + } + + @Override + protected Type visitCurrentUser(CurrentUser node, StackableAstVisitorContext<Context> context) { + return setExpressionType(node, TEXT); + } + + @Override + protected Type visitTrim(Trim node, StackableAstVisitorContext<Context> context) { + // TODO implement TRIM + throw new SemanticException("Trim is not implemented yet"); + // ImmutableList.Builder<Type> argumentTypes = ImmutableList.builder(); + // + // argumentTypes.add(process(node.getTrimSource(), context)); + // node.getTrimCharacter().ifPresent(trimChar -> argumentTypes.add(process(trimChar, + // context))); + // List<Type> actualTypes = argumentTypes.build(); + // + // String functionName = node.getSpecification().getFunctionName(); + // ResolvedFunction function = + // plannerContext.getMetadata().resolveBuiltinFunction(functionName, + // fromTypes(actualTypes)); + // + // List<Type> expectedTypes = function.getSignature().getArgumentTypes(); + // checkState(expectedTypes.size() == actualTypes.size(), "wrong argument number in the + // resolved signature"); + // + // Type actualTrimSourceType = actualTypes.get(0); + // Type expectedTrimSourceType = expectedTypes.get(0); + // coerceType(node.getTrimSource(), actualTrimSourceType, expectedTrimSourceType, + // "source argument of trim function"); + // + // if (node.getTrimCharacter().isPresent()) { + // Type actualTrimCharType = actualTypes.get(1); + // Type expectedTrimCharType = expectedTypes.get(1); + // coerceType(node.getTrimCharacter().get(), actualTrimCharType, expectedTrimCharType, + // "trim character argument of trim function"); + // } + // resolvedFunctions.put(NodeRef.of(node), function); + // + // return setExpressionType(node, function.getSignature().getReturnType()); + } + + @Override + protected Type visitParameter(Parameter node, StackableAstVisitorContext<Context> context) { + + if (parameters.isEmpty()) { + throw new SemanticException("Query takes no parameters"); + } + if (node.getId() >= parameters.size()) { + throw new SemanticException( + String.format( + "Invalid parameter index %s, max value is %s", + node.getId(), parameters.size() - 1)); + } + + Expression providedValue = parameters.get(NodeRef.of(node)); + if (providedValue == null) { + throw new SemanticException("No value provided for parameter"); + } + Type resultType = process(providedValue, context); + return setExpressionType(node, resultType); + } + + @Override + protected Type visitBetweenPredicate( + BetweenPredicate node, StackableAstVisitorContext<Context> context) { + Type valueType = process(node.getValue(), context); + Type minType = process(node.getMin(), context); + Type maxType = process(node.getMax(), context); + + if (valueType.equals(minType) || valueType.equals(maxType)) { + throw new SemanticException( + String.format("Cannot check if %s is BETWEEN %s and %s", valueType, minType, maxType)); + } + + if (!valueType.isOrderable()) { + throw new SemanticException( + String.format("Cannot check if %s is BETWEEN %s and %s", valueType, minType, maxType)); + } + + return setExpressionType(node, BOOLEAN); + } + + @Override + public Type visitCast(Cast node, StackableAstVisitorContext<Context> context) { + // TODO implement cast + throw new SemanticException("Cast is not implemented yet"); + // + // Type type; + // try { + // type = plannerContext.getTypeManager().getType(toTypeSignature(node.getType())); + // } catch (TypeNotFoundException e) { + // throw semanticException(TYPE_MISMATCH, node, "Unknown type: %s", node.getType()); + // } + // + // if (type.equals(UnknownType.UNKNOWN)) { + // throw new SemanticException("UNKNOWN is not a valid type"); + // } + // + // Type value = process(node.getExpression(), context); + // if (!value.equals(UnknownType.UNKNOWN) && !node.isTypeOnly()) { + // // TODO implement cast + // try { + // plannerContext.getMetadata().getCoercion(value, type); + // } catch (OperatorNotFoundException e) { + // throw semanticException(TYPE_MISMATCH, node, "Cannot cast %s to %s", value, type); + // } + // } + // + // return setExpressionType(node, type); + } + + @Override + protected Type visitInPredicate(InPredicate node, StackableAstVisitorContext<Context> context) { + Expression value = node.getValue(); + Expression valueList = node.getValueList(); + + // When an IN-predicate containing a subquery: `x IN (SELECT ...)` is planned, both `value` + // and `valueList` are pre-planned. + // In the row pattern matching context, expressions can contain labeled column references, + // navigations, CALSSIFIER(), and MATCH_NUMBER() calls. + // None of these can be pre-planned. Instead, the query fails: + // - if such elements are in the `value list` (subquery), the analysis of the subquery fails + // as it is done in a non-pattern-matching context. + // - if such elements are in `value`, they are captured by the below check. + // + // NOTE: Theoretically, if the IN-predicate does not contain CLASSIFIER() or MATCH_NUMBER() + // calls, it could be pre-planned + // on the condition that all column references of the `value` are consistently navigated + // (i.e., the expression is effectively evaluated within a single row), + // and that the same navigation should be applied to the resulting symbol. + // Currently, we only support the case when there are no explicit labels or navigations. This + // is a special case of such + // consistent navigating, as the column reference `x` defaults to `RUNNING + // LAST(universal_pattern_variable.x)`. + + if (valueList instanceof InListExpression) { + InListExpression inListExpression = (InListExpression) valueList; + Type type = + coerceToSingleType( + context, + "IN value and list items", + ImmutableList.<Expression>builder() + .add(value) + .addAll(inListExpression.getValues()) + .build()); + setExpressionType(inListExpression, type); + } else if (valueList instanceof SubqueryExpression) { + subqueryInPredicates.add(NodeRef.of(node)); + analyzePredicateWithSubquery( + node, process(value, context), (SubqueryExpression) valueList, context); + } else { + throw new IllegalArgumentException( + "Unexpected value list type for InPredicate: " + + node.getValueList().getClass().getName()); + } + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitSubqueryExpression( + SubqueryExpression node, StackableAstVisitorContext<Context> context) { + Type type = analyzeSubquery(node, context); + + // the implied type of a scalar subquery is that of the unique field in the single-column row + if (type instanceof RowType && ((RowType) type).getFields().size() == 1) { + type = type.getTypeParameters().get(0); + } + + setExpressionType(node, type); + subqueries.add(NodeRef.of(node)); + return type; + } + + /** @return the common supertype between the value type and subquery type */ + private Type analyzePredicateWithSubquery( + Expression node, + Type declaredValueType, + SubqueryExpression subquery, + StackableAstVisitorContext<Context> context) { + Type valueRowType = declaredValueType; + if (!(declaredValueType instanceof RowType) && !(declaredValueType instanceof UnknownType)) { + valueRowType = RowType.anonymous(ImmutableList.of(declaredValueType)); + } + + Type subqueryType = analyzeSubquery(subquery, context); + setExpressionType(subquery, subqueryType); + + if (subqueryType.equals(valueRowType)) { + throw new SemanticException( + String.format( + "Value expression and result of subquery must be of the same type: %s vs %s", + valueRowType, subqueryType)); + } + + return subqueryType; + } + + private Type analyzeSubquery( + SubqueryExpression node, StackableAstVisitorContext<Context> context) { + StatementAnalyzer analyzer = + statementAnalyzerFactory.apply(node, context.getContext().getCorrelationSupport()); + Scope subqueryScope = Scope.builder().withParent(context.getContext().getScope()).build(); + Scope queryScope = analyzer.analyze(node.getQuery(), subqueryScope); + + ImmutableList.Builder<RowType.Field> fields = ImmutableList.builder(); + for (int i = 0; i < queryScope.getRelationType().getAllFieldCount(); i++) { + Field field = queryScope.getRelationType().getFieldByIndex(i); + if (!field.isHidden()) { + if (field.getName().isPresent()) { + fields.add(RowType.field(field.getName().get(), field.getType())); + } else { + fields.add(RowType.field(field.getType())); + } + } + } + + sourceFields.addAll(queryScope.getRelationType().getVisibleFields()); + return RowType.from(fields.build()); + } + + @Override + protected Type visitExists(ExistsPredicate node, StackableAstVisitorContext<Context> context) { + StatementAnalyzer analyzer = + statementAnalyzerFactory.apply(node, context.getContext().getCorrelationSupport()); + Scope subqueryScope = Scope.builder().withParent(context.getContext().getScope()).build(); + + List<RowType.Field> fields = + analyzer.analyze(node.getSubquery(), subqueryScope).getRelationType().getAllFields() + .stream() + .map( + field -> { + if (field.getName().isPresent()) { + return RowType.field(field.getName().get(), field.getType()); + } + + return RowType.field(field.getType()); + }) + .collect(toImmutableList()); + + // TODO: this should be multiset(row(...)) + setExpressionType(node.getSubquery(), RowType.from(fields)); + + existsSubqueries.add(NodeRef.of(node)); + + return setExpressionType(node, BOOLEAN); + } + + @Override + protected Type visitQuantifiedComparisonExpression( + QuantifiedComparisonExpression node, StackableAstVisitorContext<Context> context) { + quantifiedComparisons.add(NodeRef.of(node)); + + Type declaredValueType = process(node.getValue(), context); + Type comparisonType = + analyzePredicateWithSubquery( + node, declaredValueType, (SubqueryExpression) node.getSubquery(), context); + + switch (node.getOperator()) { + case LESS_THAN: + case LESS_THAN_OR_EQUAL: + case GREATER_THAN: + case GREATER_THAN_OR_EQUAL: + if (!comparisonType.isOrderable()) { + throw new SemanticException( + String.format( + "Type [%s] must be orderable in order to be used in quantified comparison", + comparisonType)); + } + break; + case EQUAL: + case NOT_EQUAL: + if (!comparisonType.isComparable()) { + throw new SemanticException( + String.format( + "Type [%s] must be comparable in order to be used in quantified comparison", + comparisonType)); + } + break; + default: + throw new IllegalStateException( + format("Unexpected comparison type: %s", node.getOperator())); + } + + return setExpressionType(node, BOOLEAN); + } + + @Override + public Type visitFieldReference( + FieldReference node, StackableAstVisitorContext<Context> context) { + ResolvedField field = baseScope.getField(node.getFieldIndex()); + return handleResolvedField(node, field, context); + } + + @Override + protected Type visitExpression(Expression node, StackableAstVisitorContext<Context> context) { + throw new SemanticException( + String.format("not yet implemented: %s", node.getClass().getName())); + } + + @Override + protected Type visitNode(Node node, StackableAstVisitorContext<Context> context) { + throw new SemanticException( + String.format("not yet implemented: %s", node.getClass().getName())); + } + + private Type getOperator( + StackableAstVisitorContext<Context> context, + Expression node, + OperatorType operatorType, + Expression... arguments) { + ImmutableList.Builder<Type> argumentTypes = ImmutableList.builder(); + for (Expression expression : arguments) { + argumentTypes.add(process(expression, context)); + } + + BoundSignature operatorSignature; + try { + operatorSignature = + metadata.resolveOperator(operatorType, argumentTypes.build()).getSignature(); + } catch (OperatorNotFoundException e) { + throw new SemanticException(e.getMessage()); + } + + for (int i = 0; i < arguments.length; i++) { + Expression expression = arguments[i]; + Type type = operatorSignature.getArgumentTypes().get(i); + coerceType( + context, + expression, + type, + String.format("Operator %s argument %d", operatorSignature, i)); + } + + Type type = operatorSignature.getReturnType(); + return setExpressionType(node, type); + } + + private void coerceType( + Expression expression, Type actualType, Type expectedType, String message) { + if (!actualType.equals(expectedType)) { + // if (!typeCoercion.canCoerce(actualType, expectedType)) { + throw new SemanticException( + String.format( + "%s must evaluate to a %s (actual: %s)", message, expectedType, actualType)); + // } + // addOrReplaceExpressionCoercion(expression, actualType, expectedType); + } + } + + private void coerceType( + StackableAstVisitorContext<Context> context, + Expression expression, + Type expectedType, + String message) { + Type actualType = process(expression, context); + coerceType(expression, actualType, expectedType, message); + } + + private Type coerceToSingleType( + StackableAstVisitorContext<Context> context, + Node node, + String message, + Expression first, + Expression second) { + Type firstType = UNKNOWN; + if (first != null) { + firstType = process(first, context); + } + Type secondType = UNKNOWN; + if (second != null) { + secondType = process(second, context); + } + + if (!firstType.equals(secondType)) { + throw new SemanticException(String.format("%s: %s vs %s", message, firstType, secondType)); + } + + return firstType; + } + + private Type coerceToSingleType( + StackableAstVisitorContext<Context> context, + String description, + List<Expression> expressions) { + // determine super type + Type superType = UNKNOWN; + + // Use LinkedHashMultimap to preserve order in which expressions are analyzed within IN list + Multimap<Type, NodeRef<Expression>> typeExpressions = LinkedHashMultimap.create(); + for (Expression expression : expressions) { + // We need to wrap as NodeRef since LinkedHashMultimap does not allow duplicated values + Type type = process(expression, context); + typeExpressions.put(type, NodeRef.of(expression)); + } + + Set<Type> types = typeExpressions.keySet(); + + for (Type type : types) { + if (superType == UNKNOWN) { + superType = type; + } else { + if (!superType.equals(type)) { + throw new SemanticException( + String.format( + "%s must be the same type or coercible to a common type. Cannot find common type between %s and %s, all types (without duplicates): %s", + description, superType, type, typeExpressions.keySet())); + } + } + // Optional<Type> newSuperType = typeCoercion.getCommonSuperType(superType, type); + // if (newSuperType.isEmpty()) { + // throw semanticException(TYPE_MISMATCH, Iterables.get(typeExpressions.get(type), + // 0).getNode(), + // "%s must be the same type or coercible to a common type. Cannot find common + // type between %s and %s, all types (without duplicates): %s", + // description, + // superType, + // type, + // typeExpressions.keySet()); + // } + // superType = newSuperType.get(); + } + + // verify all expressions can be coerced to the superType + // for (Type type : types) { + // Collection<NodeRef<Expression>> coercionCandidates = typeExpressions.get(type); + + // if (!type.equals(superType)) { + // if (!typeCoercion.canCoerce(type, superType)) { + + // } + // addOrReplaceExpressionsCoercion(coercionCandidates, type, superType); + // } + // } + + return superType; + } + + // private void addOrReplaceExpressionCoercion(Expression expression, Type type, Type + // superType) { + // addOrReplaceExpressionsCoercion(ImmutableList.of(NodeRef.of(expression)), type, + // superType); + // } + // + // private void addOrReplaceExpressionsCoercion(Collection<NodeRef<Expression>> expressions, + // Type type, + // Type superType) { + // expressions.forEach(expression -> expressionCoercions.put(expression, superType)); + // if (typeCoercion.isTypeOnlyCoercion(type, superType)) { + // typeOnlyCoercions.addAll(expressions); + // } else { + // typeOnlyCoercions.removeAll(expressions); + // } + // } + } + + private static class Context { + private final Scope scope; + + // functionInputTypes and nameToLambdaDeclarationMap can be null or non-null independently. All + // 4 combinations are possible. + + // The list of types when expecting a lambda (i.e. processing lambda parameters of a function); + // null otherwise. + // Empty list represents expecting a lambda with no arguments. + private final List<Type> functionInputTypes; + // The mapping from names to corresponding lambda argument declarations when inside a lambda; + // null otherwise. + // Empty map means that the all lambda expressions surrounding the current node has no + // arguments. + // private final Map<FieldId, LambdaArgumentDeclaration> fieldToLambdaArgumentDeclaration; + + // Primary row pattern variables and named unions (subsets) of variables + // necessary for the analysis of expressions in the context of row pattern recognition + private final Set<String> labels; + + private final CorrelationSupport correlationSupport; + + private Context( + Scope scope, + List<Type> functionInputTypes, + Set<String> labels, + CorrelationSupport correlationSupport) { + this.scope = requireNonNull(scope, "scope is null"); + this.functionInputTypes = functionInputTypes; + // this.fieldToLambdaArgumentDeclaration = fieldToLambdaArgumentDeclaration; + this.labels = labels; + this.correlationSupport = requireNonNull(correlationSupport, "correlationSupport is null"); + } + + public static Context notInLambda(Scope scope, CorrelationSupport correlationSupport) { + return new Context(scope, null, null, correlationSupport); + } + + public Context expectingLambda(List<Type> functionInputTypes) { + return new Context( + scope, + requireNonNull(functionInputTypes, "functionInputTypes is null"), + labels, + correlationSupport); + } + + public Context notExpectingLambda() { + return new Context(scope, null, labels, correlationSupport); + } + + public static Context patternRecognition(Scope scope, Set<String> labels) { + return new Context( + scope, null, requireNonNull(labels, "labels is null"), CorrelationSupport.DISALLOWED); + } + + public Context patternRecognition(Set<String> labels) { + return new Context( + scope, + functionInputTypes, + requireNonNull(labels, "labels is null"), + CorrelationSupport.DISALLOWED); + } + + public Context notExpectingLabels() { + return new Context(scope, functionInputTypes, null, correlationSupport); + } + + Scope getScope() { + return scope; + } + + // public boolean isInLambda() { + // return fieldToLambdaArgumentDeclaration != null; + // } + + public boolean isExpectingLambda() { + return functionInputTypes != null; + } + + public List<Type> getFunctionInputTypes() { + checkState(isExpectingLambda()); + return functionInputTypes; + } + + // public Set<String> getLabels() { + // checkState(isPatternRecognition()); + // return labels; + // } + + public CorrelationSupport getCorrelationSupport() { + return correlationSupport; + } + } + + public static ExpressionAnalysis analyzeExpressions( + Metadata metadata, + SessionInfo session, + AccessControl accessControl, + TypeProvider types, + Iterable<Expression> expressions, + Map<NodeRef<Parameter>, Expression> parameters, + WarningCollector warningCollector) { + Analysis analysis = new Analysis(null, parameters); + ExpressionAnalyzer analyzer = + new ExpressionAnalyzer(metadata, accessControl, analysis, session, types, warningCollector); + for (Expression expression : expressions) { + analyzer.analyze( + expression, + Scope.builder().withRelationType(RelationId.anonymous(), new RelationType()).build()); + } + + return new ExpressionAnalysis( + analyzer.getExpressionTypes(), + analyzer.getSubqueryInPredicates(), + analyzer.getSubqueries(), + analyzer.getExistsSubqueries(), + analyzer.getColumnReferences(), + analyzer.getQuantifiedComparisons()); + } + public static ExpressionAnalysis analyzeExpression( + Metadata metadata, SessionInfo session, AccessControl accessControl, Scope scope, @@ -39,25 +1426,131 @@ public class ExpressionAnalyzer { 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; + ExpressionAnalyzer analyzer = + new ExpressionAnalyzer( + metadata, 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.getSubqueryInPredicates(), + analyzer.getSubqueries(), + analyzer.getExistsSubqueries(), + analyzer.getColumnReferences(), + analyzer.getQuantifiedComparisons()); + } + + public static void analyzeExpressionWithoutSubqueries( + Metadata metadata, + SessionInfo session, + AccessControl accessControl, + Scope scope, + Analysis analysis, + Expression expression, + String message, + WarningCollector warningCollector, + CorrelationSupport correlationSupport) { + ExpressionAnalyzer analyzer = + new ExpressionAnalyzer( + metadata, + accessControl, + (node, ignored) -> { + throw new SemanticException(message); + }, + session, + TypeProvider.empty(), + analysis.getParameters(), + warningCollector, + analysis::getType); + analyzer.analyze(expression, scope, correlationSupport); + + updateAnalysis(analysis, analyzer, session, accessControl); + analysis.addExpressionFields(expression, analyzer.getSourceFields()); + } + + private static void updateAnalysis( + Analysis analysis, + ExpressionAnalyzer analyzer, + SessionInfo session, + AccessControl accessControl) { + analysis.addTypes(analyzer.getExpressionTypes()); + analysis.addColumnReferences(analyzer.getColumnReferences()); + analysis.addTableColumnReferences( + accessControl, session.getIdentity(), analyzer.getTableColumnReferences()); + } + + public static ExpressionAnalyzer createConstantAnalyzer( + Metadata metadata, + AccessControl accessControl, + SessionInfo session, + Map<NodeRef<Parameter>, Expression> parameters, + WarningCollector warningCollector) { + return createWithoutSubqueries( + metadata, + accessControl, + session, + parameters, + "Constant expression cannot contain a subquery", + warningCollector); + } + + public static ExpressionAnalyzer createWithoutSubqueries( + Metadata metadata, + AccessControl accessControl, + SessionInfo session, + Map<NodeRef<Parameter>, Expression> parameters, + String message, + WarningCollector warningCollector) { + return createWithoutSubqueries( + metadata, + accessControl, + session, + TypeProvider.empty(), + parameters, + node -> new SemanticException(message), + warningCollector); + } + + public static ExpressionAnalyzer createWithoutSubqueries( + Metadata metadata, + AccessControl accessControl, + SessionInfo session, + TypeProvider symbolTypes, + Map<NodeRef<Parameter>, Expression> parameters, + Function<Node, ? extends RuntimeException> statementAnalyzerRejection, + WarningCollector warningCollector) { + return new ExpressionAnalyzer( + metadata, + accessControl, + (node, correlationSupport) -> { + throw statementAnalyzerRejection.apply(node); + }, + session, + symbolTypes, + parameters, + warningCollector, + expression -> { + throw new IllegalStateException("Cannot access preanalyzed types"); + }); + } + + public static boolean isNumericType(Type type) { + return type.equals(INT32) || type.equals(INT64) || type.equals(FLOAT) || type.equals(DOUBLE); + } + + private static boolean isExactNumericWithScaleZero(Type type) { + return type.equals(INT32) || type.equals(INT64); + } + + public static boolean isStringType(Type type) { + return isCharacterStringType(type); + } + + public static boolean isCharacterStringType(Type type) { + return type instanceof BinaryType; } public static class LabelPrefixedReference { 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 89628784651..5b465e02be7 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 @@ -1911,6 +1911,7 @@ public class StatementAnalyzer { ExpressionAnalysis expressionAnalysis = ExpressionAnalyzer.analyzeExpression( + metadata, sessionContext, accessControl, orderByScope, @@ -2096,6 +2097,7 @@ public class StatementAnalyzer { private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope) { return ExpressionAnalyzer.analyzeExpression( + metadata, sessionContext, accessControl, scope, @@ -2108,6 +2110,7 @@ public class StatementAnalyzer { private ExpressionAnalysis analyzeExpression( Expression expression, Scope scope, CorrelationSupport correlationSupport) { return ExpressionAnalyzer.analyzeExpression( + metadata, sessionContext, accessControl, scope, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java new file mode 100644 index 00000000000..2765386b968 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java @@ -0,0 +1,97 @@ +/* + * 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.function; + +import org.apache.iotdb.tsfile.read.common.type.Type; + +import java.util.List; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public class BoundSignature { + + private final String functionName; + private final Type returnType; + private final List<Type> argumentTypes; + + public BoundSignature(String functionName, Type returnType, List<Type> argumentTypes) { + this.functionName = requireNonNull(functionName, "functionName is null"); + this.returnType = requireNonNull(returnType, "returnType is null"); + this.argumentTypes = argumentTypes; + } + + /** The absolute canonical name of the function. */ + public String getName() { + return functionName; + } + + public Type getReturnType() { + return returnType; + } + + public int getArity() { + return argumentTypes.size(); + } + + public Type getArgumentType(int index) { + return argumentTypes.get(index); + } + + public List<Type> getArgumentTypes() { + return argumentTypes; + } + + // public Signature toSignature() { + // return Signature.builder() + // .returnType(returnType) + // .argumentTypes(argumentTypes.stream() + // .map(Type::getTypeSignature) + // .collect(Collectors.toList())) + // .build(); + // } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BoundSignature that = (BoundSignature) o; + return Objects.equals(functionName, that.functionName) + && Objects.equals(returnType, that.returnType) + && Objects.equals(argumentTypes, that.argumentTypes); + } + + @Override + public int hashCode() { + return Objects.hash(functionName, returnType, argumentTypes); + } + + @Override + public String toString() { + return functionName + + argumentTypes.stream().map(Type::toString).collect(joining(", ", "(", "):")) + + returnType; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java new file mode 100644 index 00000000000..cfc31ab471c --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java @@ -0,0 +1,68 @@ +/* + * 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.function; + +import java.util.Locale; + +import static java.util.Objects.requireNonNull; + +public class FunctionId { + private final String id; + + public FunctionId(String id) { + requireNonNull(id, "id is null"); + if (id.isEmpty()) { + throw new IllegalArgumentException("id must not be empty"); + } + if (!id.toLowerCase(Locale.US).equals(id)) { + throw new IllegalArgumentException("id must be lowercase"); + } + if (id.contains("@")) { + throw new IllegalArgumentException("id must not contain '@'"); + } + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FunctionId that = (FunctionId) o; + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return id; + } + + public static FunctionId toFunctionId(String canonicalName, Signature signature) { + return new FunctionId((canonicalName + signature).toLowerCase(Locale.US)); + } +} diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java similarity index 85% copy from iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java index eeacbb14817..1e7c3da34a7 100644 --- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java @@ -17,20 +17,11 @@ * under the License. */ -package org.apache.iotdb.tsfile.read.common.type; +package org.apache.iotdb.db.queryengine.plan.relational.function; -public enum TypeEnum { - INT32, - - INT64, - - FLOAT, - - DOUBLE, - - BOOLEAN, - - BINARY, - - ROW +public enum FunctionKind { + SCALAR, + AGGREGATE, + WINDOW, + TABLE, } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java new file mode 100644 index 00000000000..1812a409926 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java @@ -0,0 +1,68 @@ +/* + * 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.function; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class LongVariableConstraint { + private final String name; + private final String expression; + + LongVariableConstraint(String name, String expression) { + this.name = requireNonNull(name, "name is null"); + this.expression = requireNonNull(expression, "expression is null"); + } + + @JsonProperty + public String getName() { + return name; + } + + @JsonProperty + public String getExpression() { + return expression; + } + + @Override + public String toString() { + return name + ":" + expression; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LongVariableConstraint that = (LongVariableConstraint) o; + return Objects.equals(name, that.name) && Objects.equals(expression, that.expression); + } + + @Override + public int hashCode() { + return Objects.hash(name, expression); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java new file mode 100644 index 00000000000..0a727e8524a --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java @@ -0,0 +1,67 @@ +/* + * 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.function; + +public enum OperatorType { + ADD("+", 2), + SUBTRACT("-", 2), + MULTIPLY("*", 2), + DIVIDE("/", 2), + MODULUS("%", 2), + NEGATION("-", 1), + EQUAL("=", 2), + /** + * Normal comparison operator, but unordered values such as NaN are placed after all normal + * values. + */ + COMPARISON_UNORDERED_LAST("COMPARISON_UNORDERED_LAST", 2), + /** + * Normal comparison operator, but unordered values such as NaN are placed before all normal + * values. + */ + COMPARISON_UNORDERED_FIRST("COMPARISON_UNORDERED_FIRST", 2), + LESS_THAN("<", 2), + LESS_THAN_OR_EQUAL("<=", 2), + CAST("CAST", 1), + SUBSCRIPT("[]", 2), + HASH_CODE("HASH CODE", 1), + SATURATED_FLOOR_CAST("SATURATED FLOOR CAST", 1), + IS_DISTINCT_FROM("IS DISTINCT FROM", 2), + XX_HASH_64("XX HASH 64", 1), + INDETERMINATE("INDETERMINATE", 1), + READ_VALUE("READ VALUE", 1), +/**/ ; + + private final String operator; + private final int argumentCount; + + OperatorType(String operator, int argumentCount) { + this.operator = operator; + this.argumentCount = argumentCount; + } + + public String getOperator() { + return operator; + } + + public int getArgumentCount() { + return argumentCount; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java new file mode 100644 index 00000000000..dfa7667072f --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java @@ -0,0 +1,220 @@ +/* + * 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.function; + +import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Stream.concat; + +public class Signature { + + private final List<TypeVariableConstraint> typeVariableConstraints; + private final List<LongVariableConstraint> longVariableConstraints; + private final TypeSignature returnType; + private final List<TypeSignature> argumentTypes; + private final boolean variableArity; + + private Signature( + List<TypeVariableConstraint> typeVariableConstraints, + List<LongVariableConstraint> longVariableConstraints, + TypeSignature returnType, + List<TypeSignature> argumentTypes, + boolean variableArity) { + requireNonNull(typeVariableConstraints, "typeVariableConstraints is null"); + requireNonNull(longVariableConstraints, "longVariableConstraints is null"); + + this.typeVariableConstraints = new ArrayList<>(typeVariableConstraints); + this.longVariableConstraints = new ArrayList<>(longVariableConstraints); + this.returnType = requireNonNull(returnType, "returnType is null"); + this.argumentTypes = new ArrayList<>(requireNonNull(argumentTypes, "argumentTypes is null")); + this.variableArity = variableArity; + } + + @JsonProperty + public TypeSignature getReturnType() { + return returnType; + } + + @JsonProperty + public List<TypeSignature> getArgumentTypes() { + return argumentTypes; + } + + @JsonProperty + public boolean isVariableArity() { + return variableArity; + } + + @JsonProperty + public List<TypeVariableConstraint> getTypeVariableConstraints() { + return typeVariableConstraints; + } + + @JsonProperty + public List<LongVariableConstraint> getLongVariableConstraints() { + return longVariableConstraints; + } + + @Override + public int hashCode() { + return Objects.hash( + typeVariableConstraints, longVariableConstraints, returnType, argumentTypes, variableArity); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Signature)) { + return false; + } + Signature other = (Signature) obj; + return Objects.equals(this.typeVariableConstraints, other.typeVariableConstraints) + && Objects.equals(this.longVariableConstraints, other.longVariableConstraints) + && Objects.equals(this.returnType, other.returnType) + && Objects.equals(this.argumentTypes, other.argumentTypes) + && Objects.equals(this.variableArity, other.variableArity); + } + + @Override + public String toString() { + List<String> allConstraints = + concat( + typeVariableConstraints.stream().map(TypeVariableConstraint::toString), + longVariableConstraints.stream().map(LongVariableConstraint::toString)) + .collect(Collectors.toList()); + + return (allConstraints.isEmpty() ? "" : allConstraints.stream().collect(joining(",", "<", ">"))) + + argumentTypes.stream().map(Objects::toString).collect(joining(",", "(", ")")) + + ":" + + returnType; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private final List<TypeVariableConstraint> typeVariableConstraints = new ArrayList<>(); + private final List<LongVariableConstraint> longVariableConstraints = new ArrayList<>(); + private TypeSignature returnType; + private final List<TypeSignature> argumentTypes = new ArrayList<>(); + private boolean variableArity; + + private Builder() {} + + public Builder typeVariable(String name) { + typeVariableConstraints.add(TypeVariableConstraint.builder(name).build()); + return this; + } + + public Builder comparableTypeParameter(String name) { + typeVariableConstraints.add( + TypeVariableConstraint.builder(name).comparableRequired().build()); + return this; + } + + public Builder orderableTypeParameter(String name) { + typeVariableConstraints.add(TypeVariableConstraint.builder(name).orderableRequired().build()); + return this; + } + + public Builder castableToTypeParameter(String name, TypeSignature toType) { + typeVariableConstraints.add(TypeVariableConstraint.builder(name).castableTo(toType).build()); + return this; + } + + public Builder castableFromTypeParameter(String name, TypeSignature fromType) { + typeVariableConstraints.add( + TypeVariableConstraint.builder(name).castableFrom(fromType).build()); + return this; + } + + public Builder variadicTypeParameter(String name, String variadicBound) { + typeVariableConstraints.add( + TypeVariableConstraint.builder(name).variadicBound(variadicBound).build()); + return this; + } + + public Builder typeVariableConstraint(TypeVariableConstraint typeVariableConstraint) { + this.typeVariableConstraints.add( + requireNonNull(typeVariableConstraint, "typeVariableConstraint is null")); + return this; + } + + public Builder typeVariableConstraints(List<TypeVariableConstraint> typeVariableConstraints) { + this.typeVariableConstraints.addAll( + requireNonNull(typeVariableConstraints, "typeVariableConstraints is null")); + return this; + } + + // public Builder returnType(Type returnType) { + // return returnType(returnType.getTypeSignature()); + // } + + public Builder returnType(TypeSignature returnType) { + this.returnType = requireNonNull(returnType, "returnType is null"); + return this; + } + + public Builder longVariable(String name, String expression) { + this.longVariableConstraints.add(new LongVariableConstraint(name, expression)); + return this; + } + + // public Builder argumentType(Type type) { + // return argumentType(type.getTypeSignature()); + // } + + public Builder argumentType(TypeSignature type) { + argumentTypes.add(requireNonNull(type, "type is null")); + return this; + } + + public Builder argumentTypes(List<TypeSignature> argumentTypes) { + this.argumentTypes.addAll(requireNonNull(argumentTypes, "argumentTypes is null")); + return this; + } + + public Builder variableArity() { + this.variableArity = true; + return this; + } + + public Signature build() { + return new Signature( + typeVariableConstraints, + longVariableConstraints, + returnType, + argumentTypes, + variableArity); + } + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java new file mode 100644 index 00000000000..47354e4da30 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java @@ -0,0 +1,200 @@ +/* + * 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.function; + +import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public class TypeVariableConstraint { + + private final String name; + private final boolean comparableRequired; + private final boolean orderableRequired; + private final Optional<String> variadicBound; + private final Set<TypeSignature> castableTo; + private final Set<TypeSignature> castableFrom; + + private TypeVariableConstraint( + String name, + boolean comparableRequired, + boolean orderableRequired, + Optional<String> variadicBound, + Set<TypeSignature> castableTo, + Set<TypeSignature> castableFrom) { + this.name = requireNonNull(name, "name is null"); + this.comparableRequired = comparableRequired; + this.orderableRequired = orderableRequired; + this.variadicBound = requireNonNull(variadicBound, "variadicBound is null"); + if (variadicBound.map(bound -> !bound.equalsIgnoreCase("row")).orElse(false)) { + throw new IllegalArgumentException("variadicBound must be row but is " + variadicBound.get()); + } + this.castableTo = new HashSet<>(requireNonNull(castableTo, "castableTo is null")); + this.castableFrom = new HashSet<>(requireNonNull(castableFrom, "castableFrom is null")); + } + + @JsonProperty + public String getName() { + return name; + } + + @JsonProperty + public boolean isComparableRequired() { + return comparableRequired; + } + + @JsonProperty + public boolean isOrderableRequired() { + return orderableRequired; + } + + @JsonProperty + public Optional<String> getVariadicBound() { + return variadicBound; + } + + @JsonProperty + public Set<TypeSignature> getCastableTo() { + return castableTo; + } + + @JsonProperty + public Set<TypeSignature> getCastableFrom() { + return castableFrom; + } + + @Override + public String toString() { + String value = name; + if (comparableRequired) { + value += ":comparable"; + } + if (orderableRequired) { + value += ":orderable"; + } + if (variadicBound.isPresent()) { + value += ":" + variadicBound + "<*>"; + } + if (!castableTo.isEmpty()) { + value += + castableTo.stream().map(Object::toString).collect(joining(", ", ":castableTo(", ")")); + } + if (!castableFrom.isEmpty()) { + value += + castableFrom.stream().map(Object::toString).collect(joining(", ", ":castableFrom(", ")")); + } + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TypeVariableConstraint that = (TypeVariableConstraint) o; + return comparableRequired == that.comparableRequired + && orderableRequired == that.orderableRequired + && Objects.equals(name, that.name) + && Objects.equals(variadicBound, that.variadicBound) + && Objects.equals(castableTo, that.castableTo) + && Objects.equals(castableFrom, that.castableFrom); + } + + @Override + public int hashCode() { + return Objects.hash( + name, comparableRequired, orderableRequired, variadicBound, castableTo, castableFrom); + } + + public static TypeVariableConstraint typeVariable(String name) { + return builder(name).build(); + } + + public static TypeVariableConstraintBuilder builder(String name) { + return new TypeVariableConstraintBuilder(name); + } + + public static class TypeVariableConstraintBuilder { + private final String name; + private boolean comparableRequired; + private boolean orderableRequired; + private String variadicBound; + private final Set<TypeSignature> castableTo = new HashSet<>(); + private final Set<TypeSignature> castableFrom = new HashSet<>(); + + private TypeVariableConstraintBuilder(String name) { + this.name = name; + } + + public TypeVariableConstraintBuilder comparableRequired() { + this.comparableRequired = true; + return this; + } + + public TypeVariableConstraintBuilder orderableRequired() { + this.orderableRequired = true; + return this; + } + + public TypeVariableConstraintBuilder variadicBound(String variadicBound) { + this.variadicBound = variadicBound; + return this; + } + + // public TypeVariableConstraintBuilder castableTo(Type type) { + // return castableTo(type.getTypeSignature()); + // } + + public TypeVariableConstraintBuilder castableTo(TypeSignature type) { + this.castableTo.add(type); + return this; + } + + // public TypeVariableConstraintBuilder castableFrom(Type type) { + // return castableFrom(type.getTypeSignature()); + // } + + public TypeVariableConstraintBuilder castableFrom(TypeSignature type) { + this.castableFrom.add(type); + return this; + } + + public TypeVariableConstraint build() { + return new TypeVariableConstraint( + name, + comparableRequired, + orderableRequired, + Optional.ofNullable(variadicBound), + castableTo, + castableFrom); + } + } +} 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 index f4880b58b6e..5e708b9c9f0 100644 --- 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 @@ -20,7 +20,10 @@ package org.apache.iotdb.db.queryengine.plan.relational.metadata; import org.apache.iotdb.db.queryengine.common.SessionInfo; +import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType; +import org.apache.iotdb.tsfile.read.common.type.Type; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -55,4 +58,7 @@ public interface Metadata { * @throws RuntimeException if table handle is no longer valid */ Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle); + + ResolvedFunction resolveOperator(OperatorType operatorType, List<? extends Type> argumentTypes) + throws OperatorNotFoundException; } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java new file mode 100644 index 00000000000..dcc57700dd8 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java @@ -0,0 +1,119 @@ +/* + * 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.commons.exception.IoTDBException; +import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType; +import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature; +import org.apache.iotdb.tsfile.read.common.type.Type; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.apache.iotdb.rpc.TSStatusCode.OPERATOR_NOT_FOUND; + +public class OperatorNotFoundException extends IoTDBException { + private final OperatorType operatorType; + private final TypeSignature returnType; + private final List<Type> argumentTypes; + + public OperatorNotFoundException( + OperatorType operatorType, List<? extends Type> argumentTypes, Throwable cause) { + super( + formatErrorMessage(operatorType, argumentTypes, Optional.empty()), + cause, + OPERATOR_NOT_FOUND.getStatusCode()); + this.operatorType = requireNonNull(operatorType, "operatorType is null"); + this.returnType = null; + this.argumentTypes = + ImmutableList.copyOf(requireNonNull(argumentTypes, "argumentTypes is null")); + } + + public OperatorNotFoundException( + OperatorType operatorType, + List<? extends Type> argumentTypes, + TypeSignature returnType, + Throwable cause) { + super( + formatErrorMessage(operatorType, argumentTypes, Optional.of(returnType)), + cause, + OPERATOR_NOT_FOUND.getStatusCode()); + this.operatorType = requireNonNull(operatorType, "operatorType is null"); + this.argumentTypes = + ImmutableList.copyOf(requireNonNull(argumentTypes, "argumentTypes is null")); + this.returnType = requireNonNull(returnType, "returnType is null"); + } + + private static String formatErrorMessage( + OperatorType operatorType, + List<? extends Type> argumentTypes, + Optional<TypeSignature> returnType) { + switch (operatorType) { + case ADD: + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case MODULUS: + case EQUAL: + case LESS_THAN: + case LESS_THAN_OR_EQUAL: + return format( + "Cannot apply operator: %s %s %s", + argumentTypes.get(0), operatorType.getOperator(), argumentTypes.get(1)); + case NEGATION: + return format("Cannot negate %s", argumentTypes.get(0)); + case IS_DISTINCT_FROM: + return format( + "Cannot check if %s is distinct from %s", argumentTypes.get(0), argumentTypes.get(1)); + case CAST: + return format( + "Cannot cast %s to %s", + argumentTypes.get(0), + returnType.orElseThrow(() -> new NoSuchElementException("No value present"))); + case SUBSCRIPT: + return format( + "Cannot use %s for subscript of %s", argumentTypes.get(1), argumentTypes.get(0)); + default: + return format( + "Operator '%s'%s cannot be applied to %s", + operatorType.getOperator(), + returnType.map(value -> ":" + value).orElse(""), + Joiner.on(", ").join(argumentTypes)); + } + } + + public OperatorType getOperatorType() { + return operatorType; + } + + public TypeSignature getReturnType() { + return returnType; + } + + public List<Type> getArgumentTypes() { + return argumentTypes; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java new file mode 100644 index 00000000000..da3ccc0cad2 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java @@ -0,0 +1,107 @@ +/* + * 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.plan.relational.function.BoundSignature; +import org.apache.iotdb.db.queryengine.plan.relational.function.FunctionId; +import org.apache.iotdb.db.queryengine.plan.relational.function.FunctionKind; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class ResolvedFunction { + private final BoundSignature signature; + private final FunctionId functionId; + private final FunctionKind functionKind; + private final boolean deterministic; + + public ResolvedFunction( + BoundSignature signature, + FunctionId functionId, + FunctionKind functionKind, + boolean deterministic) { + this.signature = requireNonNull(signature, "signature is null"); + this.functionId = requireNonNull(functionId, "functionId is null"); + this.functionKind = requireNonNull(functionKind, "functionKind is null"); + this.deterministic = deterministic; + } + + public BoundSignature getSignature() { + return signature; + } + + public FunctionId getFunctionId() { + return functionId; + } + + public FunctionKind getFunctionKind() { + return functionKind; + } + + public boolean isDeterministic() { + return deterministic; + } + + // public static boolean isResolved(QualifiedName name) { + // return SerializedResolvedFunction.isSerializedResolvedFunction(name); + // } + // + // public QualifiedName toQualifiedName() { + // CatalogSchemaFunctionName name = toCatalogSchemaFunctionName(); + // return QualifiedName.of(name.getCatalogName(), name.getSchemaName(), + // name.getFunctionName()); + // } + // + // public CatalogSchemaFunctionName toCatalogSchemaFunctionName() { + // return ResolvedFunctionDecoder.toCatalogSchemaFunctionName(this); + // } + // + // public static CatalogSchemaFunctionName extractFunctionName(QualifiedName qualifiedName) { + // checkArgument(isResolved(qualifiedName), "Expected qualifiedName to be a resolved function: + // %s", qualifiedName); + // return SerializedResolvedFunction.fromSerializedName(qualifiedName).functionName(); + // } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ResolvedFunction that = (ResolvedFunction) o; + return Objects.equals(signature, that.signature) + && Objects.equals(functionId, that.functionId) + && functionKind == that.functionKind + && deterministic == that.deterministic; + } + + @Override + public int hashCode() { + return Objects.hash(signature, functionId, functionKind, deterministic); + } + + @Override + public String toString() { + return signature.toString(); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java new file mode 100644 index 00000000000..b0b8e76edc6 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java @@ -0,0 +1,79 @@ +/* + * 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.type; + +import com.google.errorprone.annotations.Immutable; + +import java.util.Objects; +import java.util.Optional; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +@Immutable +public final class NamedTypeSignature { + private final Optional<RowFieldName> fieldName; + private final TypeSignature typeSignature; + + public NamedTypeSignature(Optional<RowFieldName> fieldName, TypeSignature typeSignature) { + this.fieldName = requireNonNull(fieldName, "fieldName is null"); + this.typeSignature = requireNonNull(typeSignature, "typeSignature is null"); + } + + public Optional<RowFieldName> getFieldName() { + return fieldName; + } + + public TypeSignature getTypeSignature() { + return typeSignature; + } + + public Optional<String> getName() { + return getFieldName().map(RowFieldName::getName); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NamedTypeSignature other = (NamedTypeSignature) o; + + return Objects.equals(this.fieldName, other.fieldName) + && Objects.equals(this.typeSignature, other.typeSignature); + } + + @Override + public String toString() { + if (fieldName.isPresent()) { + return format("\"%s\" %s", fieldName.get().getName().replace("\"", "\"\""), typeSignature); + } + return typeSignature.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, typeSignature); + } +} diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java similarity index 85% copy from iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java index eeacbb14817..f52cb50946b 100644 --- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java @@ -17,20 +17,11 @@ * under the License. */ -package org.apache.iotdb.tsfile.read.common.type; +package org.apache.iotdb.db.queryengine.plan.relational.type; -public enum TypeEnum { - INT32, - - INT64, - - FLOAT, - - DOUBLE, - - BOOLEAN, - - BINARY, - - ROW +public enum ParameterKind { + TYPE, + NAMED_TYPE, + LONG, + VARIABLE } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java new file mode 100644 index 00000000000..d1896d8a9ee --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java @@ -0,0 +1,63 @@ +/* + * 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.type; + +import com.google.errorprone.annotations.Immutable; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +@Immutable +public final class RowFieldName { + private final String name; + + public RowFieldName(String name) { + this.name = requireNonNull(name, "name is null"); + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RowFieldName other = (RowFieldName) o; + + return Objects.equals(this.name, other.name); + } + + @Override + public String toString() { + return '"' + name.replace("\"", "\"\"") + '"'; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java new file mode 100644 index 00000000000..37ae2db6a21 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java @@ -0,0 +1,55 @@ +/* + * 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.type; + +public class StandardTypes { + public static final String BIGINT = "bigint"; + public static final String INTEGER = "integer"; + public static final String SMALLINT = "smallint"; + public static final String TINYINT = "tinyint"; + public static final String BOOLEAN = "boolean"; + public static final String DATE = "date"; + public static final String DECIMAL = "decimal"; + public static final String REAL = "real"; + public static final String DOUBLE = "double"; + public static final String HYPER_LOG_LOG = "HyperLogLog"; + public static final String QDIGEST = "qdigest"; + public static final String TDIGEST = "tdigest"; + public static final String P4_HYPER_LOG_LOG = "P4HyperLogLog"; + public static final String INTERVAL_DAY_TO_SECOND = "interval day to second"; + public static final String INTERVAL_YEAR_TO_MONTH = "interval year to month"; + public static final String TIMESTAMP = "timestamp"; + public static final String TIMESTAMP_WITH_TIME_ZONE = "timestamp with time zone"; + public static final String TIME = "time"; + public static final String TIME_WITH_TIME_ZONE = "time with time zone"; + public static final String VARBINARY = "varbinary"; + public static final String VARCHAR = "varchar"; + public static final String CHAR = "char"; + public static final String ROW = "row"; + public static final String ARRAY = "array"; + public static final String MAP = "map"; + public static final String JSON = "json"; + public static final String JSON_2016 = "json2016"; + public static final String IPADDRESS = "ipaddress"; + public static final String GEOMETRY = "Geometry"; + public static final String UUID = "uuid"; + + private StandardTypes() {} +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java new file mode 100644 index 00000000000..f6977d7a6a0 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java @@ -0,0 +1,207 @@ +/* + * 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.type; + +import com.google.errorprone.annotations.FormatMethod; +import com.google.errorprone.annotations.Immutable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static org.apache.iotdb.db.queryengine.plan.relational.type.StandardTypes.TIME_WITH_TIME_ZONE; +import static org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignatureParameter.typeParameter; + +@Immutable +public class TypeSignature { + private static final String TIMESTAMP_WITH_TIME_ZONE = "timestamp with time zone"; + private static final String TIMESTAMP_WITHOUT_TIME_ZONE = "timestamp without time zone"; + + private final String base; + private final List<TypeSignatureParameter> parameters; + private final boolean calculated; + + private int hashCode; + + public TypeSignature(String base, TypeSignatureParameter... parameters) { + this(base, asList(parameters)); + } + + public TypeSignature(String base, List<TypeSignatureParameter> parameters) { + checkArgument(base != null, "base is null"); + this.base = base; + checkArgument(!base.isEmpty(), "base is empty"); + checkArgument(validateName(base), "Bad characters in base type: %s", base); + checkArgument(parameters != null, "parameters is null"); + this.parameters = new ArrayList<>(parameters); + + this.calculated = parameters.stream().anyMatch(TypeSignatureParameter::isCalculated); + } + + public String getBase() { + return base; + } + + public List<TypeSignatureParameter> getParameters() { + return parameters; + } + + public List<TypeSignature> getTypeParametersAsTypeSignatures() { + List<TypeSignature> result = new ArrayList<>(); + for (TypeSignatureParameter parameter : parameters) { + if (parameter.getKind() != ParameterKind.TYPE) { + throw new IllegalStateException( + format("Expected all parameters to be TypeSignatures but [%s] was found", parameter)); + } + result.add(parameter.getTypeSignature()); + } + return result; + } + + public boolean isCalculated() { + return calculated; + } + + @Override + public String toString() { + return formatValue(false); + } + + public String jsonValue() { + return formatValue(true); + } + + private String formatValue(boolean json) { + if (parameters.isEmpty()) { + return base; + } + + // if (base.equalsIgnoreCase(StandardTypes.VARCHAR) && + // (parameters.size() == 1) && + // parameters.get(0).isLongLiteral() && + // parameters.get(0).getLongLiteral() == VarcharType.UNBOUNDED_LENGTH) { + // return base; + // } + + // TODO: this is somewhat of a hack. We need to evolve TypeSignature to be more "structural" for + // the special types, similar to DataType from the AST. + // In fact. TypeSignature should become the IR counterpart to DataType from the AST. + if (base.equalsIgnoreCase(TIMESTAMP_WITH_TIME_ZONE)) { + return format("timestamp(%s) with time zone", parameters.get(0)); + } + + if (base.equalsIgnoreCase(TIMESTAMP_WITHOUT_TIME_ZONE)) { + return format("timestamp(%s) without time zone", parameters.get(0)); + } + + if (base.equalsIgnoreCase(TIME_WITH_TIME_ZONE)) { + return format("time(%s) with time zone", parameters.get(0)); + } + + StringBuilder typeName = new StringBuilder(base); + typeName + .append("(") + .append(json ? parameters.get(0).jsonValue() : parameters.get(0).toString()); + for (int i = 1; i < parameters.size(); i++) { + typeName + .append(",") + .append(json ? parameters.get(i).jsonValue() : parameters.get(i).toString()); + } + typeName.append(")"); + return typeName.toString(); + } + + @FormatMethod + private static void checkArgument(boolean argument, String format, Object... args) { + if (!argument) { + throw new IllegalArgumentException(format(format, args)); + } + } + + private static boolean validateName(String name) { + return name.chars().noneMatch(c -> c == '<' || c == '>' || c == ','); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeSignature other = (TypeSignature) o; + + return Objects.equals( + this.base.toLowerCase(Locale.ENGLISH), other.base.toLowerCase(Locale.ENGLISH)) + && Objects.equals(this.parameters, other.parameters); + } + + @Override + public int hashCode() { + int hash = hashCode; + if (hash == 0) { + hash = Objects.hash(base.toLowerCase(Locale.ENGLISH), parameters); + if (hash == 0) { + hash = 1; + } + hashCode = hash; + } + + return hash; + } + + // Type signature constructors for common types + + public static TypeSignature parametricType(String name, TypeSignature... parameters) { + return new TypeSignature( + name, + Arrays.stream(parameters) + .map(TypeSignatureParameter::typeParameter) + .collect(Collectors.toList())); + } + + public static TypeSignature functionType(TypeSignature first, TypeSignature... rest) { + List<TypeSignatureParameter> parameters = new ArrayList<>(); + parameters.add(typeParameter(first)); + + Arrays.stream(rest).map(TypeSignatureParameter::typeParameter).forEach(parameters::add); + + return new TypeSignature("function", parameters); + } + + public static TypeSignature rowType(TypeSignatureParameter... fields) { + return rowType(Arrays.asList(fields)); + } + + public static TypeSignature rowType(List<TypeSignatureParameter> fields) { + checkArgument( + fields.stream().allMatch(parameter -> parameter.getKind() == ParameterKind.NAMED_TYPE), + "Parameters for ROW type must be NAMED_TYPE parameters"); + + return new TypeSignature(StandardTypes.ROW, fields); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java new file mode 100644 index 00000000000..9f12515e874 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java @@ -0,0 +1,175 @@ +/* + * 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.type; + +import com.google.errorprone.annotations.Immutable; + +import java.util.Objects; +import java.util.Optional; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +@Immutable +public final class TypeSignatureParameter { + private final ParameterKind kind; + private final Object value; + + public static TypeSignatureParameter typeParameter(TypeSignature typeSignature) { + return new TypeSignatureParameter(ParameterKind.TYPE, typeSignature); + } + + public static TypeSignatureParameter numericParameter(long longLiteral) { + return new TypeSignatureParameter(ParameterKind.LONG, longLiteral); + } + + public static TypeSignatureParameter namedTypeParameter(NamedTypeSignature namedTypeSignature) { + return new TypeSignatureParameter(ParameterKind.NAMED_TYPE, namedTypeSignature); + } + + public static TypeSignatureParameter namedField(String name, TypeSignature type) { + return new TypeSignatureParameter( + ParameterKind.NAMED_TYPE, + new NamedTypeSignature(Optional.of(new RowFieldName(name)), type)); + } + + public static TypeSignatureParameter anonymousField(TypeSignature type) { + return new TypeSignatureParameter( + ParameterKind.NAMED_TYPE, new NamedTypeSignature(Optional.empty(), type)); + } + + public static TypeSignatureParameter typeVariable(String variable) { + return new TypeSignatureParameter(ParameterKind.VARIABLE, variable); + } + + private TypeSignatureParameter(ParameterKind kind, Object value) { + this.kind = requireNonNull(kind, "kind is null"); + this.value = requireNonNull(value, "value is null"); + } + + @Override + public String toString() { + return value.toString(); + } + + public String jsonValue() { + String prefix = ""; + if (kind == ParameterKind.VARIABLE) { + prefix = "@"; + } + + String valueJson; + if (value instanceof TypeSignature) { + TypeSignature typeSignature = (TypeSignature) value; + valueJson = typeSignature.jsonValue(); + } else { + valueJson = value.toString(); + } + return prefix + valueJson; + } + + public ParameterKind getKind() { + return kind; + } + + public boolean isTypeSignature() { + return kind == ParameterKind.TYPE; + } + + public boolean isLongLiteral() { + return kind == ParameterKind.LONG; + } + + public boolean isNamedTypeSignature() { + return kind == ParameterKind.NAMED_TYPE; + } + + public boolean isVariable() { + return kind == ParameterKind.VARIABLE; + } + + private <A> A getValue(ParameterKind expectedParameterKind, Class<A> target) { + if (kind != expectedParameterKind) { + throw new IllegalArgumentException( + format("ParameterKind is [%s] but expected [%s]", kind, expectedParameterKind)); + } + return target.cast(value); + } + + public TypeSignature getTypeSignature() { + return getValue(ParameterKind.TYPE, TypeSignature.class); + } + + public Long getLongLiteral() { + return getValue(ParameterKind.LONG, Long.class); + } + + public NamedTypeSignature getNamedTypeSignature() { + return getValue(ParameterKind.NAMED_TYPE, NamedTypeSignature.class); + } + + public String getVariable() { + return getValue(ParameterKind.VARIABLE, String.class); + } + + public Optional<TypeSignature> getTypeSignatureOrNamedTypeSignature() { + switch (kind) { + case TYPE: + return Optional.of(getTypeSignature()); + case NAMED_TYPE: + return Optional.of(getNamedTypeSignature().getTypeSignature()); + default: + return Optional.empty(); + } + } + + public boolean isCalculated() { + switch (kind) { + case TYPE: + return getTypeSignature().isCalculated(); + case NAMED_TYPE: + return getNamedTypeSignature().getTypeSignature().isCalculated(); + case LONG: + return false; + case VARIABLE: + return true; + } + throw new IllegalArgumentException("Unexpected parameter kind: " + kind); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeSignatureParameter other = (TypeSignatureParameter) o; + + return this.kind == other.kind && Objects.equals(this.value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(kind, value); + } +} diff --git a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java new file mode 100644 index 00000000000..d4a7509a675 --- /dev/null +++ b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java @@ -0,0 +1,65 @@ +/* + * 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.relational.sql.tree; + +import java.util.LinkedList; +import java.util.Optional; + +public class StackableAstVisitor<R, C> + extends AstVisitor<R, StackableAstVisitor.StackableAstVisitorContext<C>> { + + @Override + public R process(Node node, StackableAstVisitorContext<C> context) { + context.push(node); + try { + return node.accept(this, context); + } finally { + context.pop(); + } + } + + public static class StackableAstVisitorContext<C> { + private final LinkedList<Node> stack = new LinkedList<>(); + private final C context; + + public StackableAstVisitorContext(C context) { + this.context = context; + } + + public C getContext() { + return context; + } + + private void pop() { + stack.pop(); + } + + void push(Node node) { + stack.push(node); + } + + public Optional<Node> getPreviousNode() { + if (stack.size() > 1) { + return Optional.of(stack.get(1)); + } + return Optional.empty(); + } + } +} diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java index ad8b003169a..de47824a6d1 100644 --- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java +++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java @@ -24,6 +24,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -144,4 +145,24 @@ public class RowType implements Type { public boolean isOrderable() { return orderable; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RowType rowType = (RowType) o; + return comparable == rowType.comparable + && orderable == rowType.orderable + && Objects.equals(fields, rowType.fields) + && Objects.equals(fieldTypes, rowType.fieldTypes); + } + + @Override + public int hashCode() { + return Objects.hash(fields, fieldTypes, comparable, orderable); + } } diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java index eeacbb14817..ed3e7b99fe5 100644 --- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java +++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java @@ -32,5 +32,7 @@ public enum TypeEnum { BINARY, - ROW + ROW, + + UNKNOWN } diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java index ed52a9fef95..7a82fdb8ae2 100644 --- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java +++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java @@ -46,4 +46,26 @@ public class TypeFactory { String.format("Invalid TSDataType for TypeFactory: %s", tsDataType)); } } + + public static Type getType(TypeEnum typeEnum) { + switch (typeEnum) { + case INT32: + return IntType.getInstance(); + case INT64: + return LongType.getInstance(); + case FLOAT: + return FloatType.getInstance(); + case DOUBLE: + return DoubleType.getInstance(); + case BOOLEAN: + return BooleanType.getInstance(); + case BINARY: + return BinaryType.getInstance(); + case UNKNOWN: + return UnknownType.getInstance(); + default: + throw new UnsupportedOperationException( + String.format("Invalid TypeEnum for TypeFactory: %s", typeEnum)); + } + } } diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java new file mode 100644 index 00000000000..0c31d07ddb3 --- /dev/null +++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java @@ -0,0 +1,87 @@ +/* + * 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.tsfile.read.common.type; + +import org.apache.iotdb.tsfile.read.common.block.column.BooleanColumnBuilder; +import org.apache.iotdb.tsfile.read.common.block.column.Column; +import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder; + +import java.util.Collections; +import java.util.List; + +import static org.apache.iotdb.tsfile.utils.Preconditions.checkArgument; + +public class UnknownType implements Type { + public static final UnknownType UNKNOWN = new UnknownType(); + public static final String NAME = "unknown"; + + private UnknownType() {} + + @Override + public void writeBoolean(ColumnBuilder columnBuilder, boolean value) { + // Ideally, this function should never be invoked for the unknown type. + // However, some logic (e.g. AbstractMinMaxBy) relies on writing a default value before the null + // check. + checkArgument(!value); + columnBuilder.appendNull(); + } + + @Override + public boolean getBoolean(Column column, int position) { + // Ideally, this function should never be invoked for the unknown type. + // However, some logic relies on having a default value before the null check. + checkArgument(column.isNull(position)); + return false; + } + + @Override + public ColumnBuilder createColumnBuilder(int expectedEntries) { + return new BooleanColumnBuilder(null, expectedEntries); + } + + @Override + public TypeEnum getTypeEnum() { + return TypeEnum.UNKNOWN; + } + + @Override + public String getDisplayName() { + return NAME; + } + + @Override + public boolean isComparable() { + return true; + } + + @Override + public boolean isOrderable() { + return true; + } + + @Override + public List<Type> getTypeParameters() { + return Collections.emptyList(); + } + + public static UnknownType getInstance() { + return UNKNOWN; + } +}
