diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 index ff20819e56..21ff03c484 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 @@ -145,6 +145,7 @@ DAY: 'DAY'; DATA: 'DATA'; DATABASE: 'DATABASE'; DATABASES: 'DATABASES'; +DATE: 'DATE'; DATEADD: 'DATEADD'; DATE_ADD: 'DATE_ADD'; DATEDIFF: 'DATEDIFF'; diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 18ad0089af..64fa6c8752 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -196,6 +196,7 @@ valueExpression primaryExpression : CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase | CASE value=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase + | name=CAST LEFT_PAREN expression AS identifier RIGHT_PAREN #cast | constant #constantDefault | ASTERISK #star | qualifiedName DOT ASTERISK #star @@ -205,6 +206,8 @@ primaryExpression | identifier #columnReference | base=primaryExpression DOT fieldName=identifier #dereference | LEFT_PAREN expression RIGHT_PAREN #parenthesizedExpression + | EXTRACT LEFT_PAREN field=identifier FROM (DATE | TIMESTAMP)? + source=valueExpression RIGHT_PAREN #extract ; qualifiedName @@ -324,6 +327,7 @@ ansiNonReserved | DATA | DATABASE | DATABASES + | DATE | DATEADD | DATE_ADD | DATEDIFF @@ -577,6 +581,7 @@ nonReserved | DATA | DATABASE | DATABASES + | DATE | DATEADD | DATE_ADD | DATEDIFF diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java index 9455abb74c..523c239cc6 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java @@ -529,4 +529,24 @@ public class CastExpr extends Expr { || (children.get(0).getType().isStringType() && !getType().isStringType()) || (!children.get(0).getType().isDateType() && getType().isDateType()); } + + @Override + public void finalizeImplForNereids() throws AnalysisException { + FunctionName fnName = new FunctionName(getFnName(type)); + Function searchDesc = new Function(fnName, Arrays.asList(collectChildReturnTypes()), Type.INVALID, false); + if (type.isScalarType()) { + if (isImplicit) { + fn = Env.getCurrentEnv().getFunction( + searchDesc, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF); + } else { + fn = Env.getCurrentEnv().getFunction( + searchDesc, Function.CompareMode.IS_IDENTICAL); + } + } else if (type.isArrayType()) { + fn = ScalarFunction.createBuiltin(getFnName(Type.ARRAY), + type, Function.NullableMode.ALWAYS_NULLABLE, + Lists.newArrayList(Type.VARCHAR), false, + "doris::CastFunctions::cast_to_array_val", null, null, true); + } + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java index 8407726208..ae70ca3297 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java @@ -1318,10 +1318,16 @@ public class FunctionCallExpr extends Expr { fn = getBuiltinFunction(fnName.getFunction(), new Type[]{childType}, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF); type = fn.getReturnType(); - } else if (fnName.getFunction().equalsIgnoreCase("substring")) { + } else if (fnName.getFunction().equalsIgnoreCase("substring") + || fnName.getFunction().equalsIgnoreCase("cast")) { Type[] childTypes = getChildren().stream().map(t -> t.type).toArray(Type[]::new); fn = getBuiltinFunction(fnName.getFunction(), childTypes, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF); type = fn.getReturnType(); + } else if (fnName.getFunction().equalsIgnoreCase("year")) { + Type childType = getChild(0).type; + fn = getBuiltinFunction(fnName.getFunction(), new Type[]{childType}, + Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF); + type = fn.getReturnType(); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/ExpressionTranslator.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/ExpressionTranslator.java index 9146380e7f..66f97d6973 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/ExpressionTranslator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/ExpressionTranslator.java @@ -23,6 +23,7 @@ import org.apache.doris.analysis.BinaryPredicate.Operator; import org.apache.doris.analysis.BoolLiteral; import org.apache.doris.analysis.CaseExpr; import org.apache.doris.analysis.CaseWhenClause; +import org.apache.doris.analysis.CastExpr; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.FloatLiteral; import org.apache.doris.analysis.FunctionCallExpr; @@ -38,6 +39,7 @@ import org.apache.doris.nereids.trees.expressions.Arithmetic; import org.apache.doris.nereids.trees.expressions.Between; import org.apache.doris.nereids.trees.expressions.BooleanLiteral; import org.apache.doris.nereids.trees.expressions.CaseWhen; +import org.apache.doris.nereids.trees.expressions.Cast; import org.apache.doris.nereids.trees.expressions.DateLiteral; import org.apache.doris.nereids.trees.expressions.DateTimeLiteral; import org.apache.doris.nereids.trees.expressions.DoubleLiteral; @@ -244,6 +246,13 @@ public class ExpressionTranslator extends DefaultExpressionVisitor { return new CaseWhen(whenClauses, getExpression(context.elseExpression)); } + @Override + public Expression visitCast(DorisParser.CastContext ctx) { + return ParserUtils.withOrigin(ctx, () -> + new Cast(getExpression(ctx.expression()), ctx.identifier().getText())); + } + + @Override + public UnboundFunction visitExtract(DorisParser.ExtractContext ctx) { + return ParserUtils.withOrigin(ctx, () -> { + String functionName = ctx.field.getText(); + return new UnboundFunction(functionName, false, Arrays.asList(getExpression(ctx.source))); + }); + } + @Override public UnboundFunction visitFunctionCall(DorisParser.FunctionCallContext ctx) { return ParserUtils.withOrigin(ctx, () -> { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java index 3a5b2bbeeb..4156dade49 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java @@ -26,6 +26,7 @@ import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.expressions.TimestampArithmetic; import org.apache.doris.nereids.trees.expressions.functions.Substring; import org.apache.doris.nereids.trees.expressions.functions.Sum; +import org.apache.doris.nereids.trees.expressions.functions.Year; import org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter; import org.apache.doris.nereids.trees.plans.logical.LogicalFilter; import org.apache.doris.nereids.trees.plans.logical.LogicalProject; @@ -90,7 +91,6 @@ public class BindFunction implements AnalysisRuleFactory { } return new Sum(unboundFunction.getArguments().get(0)); } else if (name.equalsIgnoreCase("substr") || name.equalsIgnoreCase("substring")) { - List arguments = unboundFunction.getArguments(); if (arguments.size() == 2) { return new Substring(unboundFunction.getArguments().get(0), @@ -100,6 +100,12 @@ public class BindFunction implements AnalysisRuleFactory { unboundFunction.getArguments().get(2)); } return unboundFunction; + } else if (name.equalsIgnoreCase("year")) { + List arguments = unboundFunction.getArguments(); + if (arguments.size() != 1) { + return unboundFunction; + } + return new Year(unboundFunction.getArguments().get(0)); } return unboundFunction; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java new file mode 100644 index 0000000000..63d0fd83c5 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java @@ -0,0 +1,69 @@ +// 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.doris.nereids.trees.expressions; + +import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.DataType; + +import com.google.common.base.Preconditions; + +import java.util.List; + +/** + * cast function. + */ +public class Cast extends Expression implements BinaryExpression { + + public Cast(Expression child, String type) { + super(child, new StringLiteral(type)); + } + + @Override + public DataType getDataType() { + StringLiteral type = (StringLiteral) right(); + return DataType.convertFromString(type.getValue()); + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitCast(this, context); + } + + @Override + public boolean nullable() { + return left().nullable(); + } + + @Override + public Expression withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + Preconditions.checkArgument(children.get(1) instanceof StringLiteral); + return new Cast(children.get(0), ((StringLiteral) children.get(1)).getValue()); + } + + @Override + public String toSql() throws UnboundException { + return "CAST(" + left().toSql() + " AS " + ((StringLiteral) right()).getValue() + ")"; + } + + @Override + public String toString() { + return toSql(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Year.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Year.java new file mode 100644 index 0000000000..13f52fb8af --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Year.java @@ -0,0 +1,53 @@ +// 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.doris.nereids.trees.expressions.functions; + +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.UnaryExpression; +import org.apache.doris.nereids.types.DataType; +import org.apache.doris.nereids.types.IntegerType; + +import com.google.common.base.Preconditions; + +import java.util.List; + +/** + * year function. + */ +public class Year extends BoundFunction implements UnaryExpression { + + public Year(Expression child) { + super("year", child); + } + + @Override + public DataType getDataType() { + return IntegerType.INSTANCE; + } + + @Override + public boolean nullable() { + return child().nullable(); + } + + @Override + public Expression withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new Year(children.get(0)); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java index 248c32ab29..bb1e3d5f05 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java @@ -28,6 +28,7 @@ import org.apache.doris.nereids.trees.expressions.Arithmetic; import org.apache.doris.nereids.trees.expressions.Between; import org.apache.doris.nereids.trees.expressions.BooleanLiteral; import org.apache.doris.nereids.trees.expressions.CaseWhen; +import org.apache.doris.nereids.trees.expressions.Cast; import org.apache.doris.nereids.trees.expressions.ComparisonPredicate; import org.apache.doris.nereids.trees.expressions.CompoundPredicate; import org.apache.doris.nereids.trees.expressions.DateLiteral; @@ -179,6 +180,10 @@ public abstract class ExpressionVisitor { return visitStringRegexPredicate(regexp, context); } + public R visitCast(Cast cast, C context) { + return visit(cast, context); + } + public R visitBoundFunction(BoundFunction boundFunction, C context) { return visit(boundFunction, context); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/DataType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/DataType.java index 8a29991a2a..a08fa9759c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/DataType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/DataType.java @@ -76,6 +76,36 @@ public abstract class DataType { } } + /** + * Convert to data type in Nereids. + * throw exception when cannot convert to Nereids type + * + * @param type data type in string representation + * @return data type in Nereids + */ + public static DataType convertFromString(String type) { + // TODO: use a better way to resolve types + switch (type.toLowerCase()) { + case "bool": + case "boolean": + return BooleanType.INSTANCE; + case "int": + return IntegerType.INSTANCE; + case "bigint": + return BigIntType.INSTANCE; + case "double": + return DoubleType.INSTANCE; + case "string": + return StringType.INSTANCE; + case "null": + return NullType.INSTANCE; + case "datetime": + return DateTimeType.INSTANCE; + default: + throw new AnalysisException("Nereids do not support type: " + type); + } + } + public abstract Type toCatalogDataType(); @Override diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionParserTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionParserTest.java index d02a76c0ee..7001f3b2ed 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionParserTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionParserTest.java @@ -206,4 +206,25 @@ public class ExpressionParserTest { interval = "tt > now() - interval 1+1 day"; assertExpr(interval); } + + @Test + public void testExtract() throws Exception { + String extract = "SELECT EXTRACT(YEAR FROM TIMESTAMP '2022-02-21 00:00:00') AS year FROM TEST;"; + assertSql(extract); + + String extract2 = "SELECT EXTRACT(YEAR FROM DATE '2022-02-21 00:00:00') AS year FROM TEST;"; + assertSql(extract2); + + String extract3 = "SELECT EXTRACT(YEAR FROM '2022-02-21 00:00:00') AS year FROM TEST;"; + assertSql(extract3); + } + + @Test + public void testCast() throws Exception { + String cast = "SELECT CAST(A AS STRING) FROM TEST;"; + assertSql(cast); + + String cast2 = "SELECT CAST(A AS INT) AS I FROM TEST;"; + assertSql(cast2); + } }