This is an automated email from the ASF dual-hosted git repository. starocean999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push: new 884d615d6ec [feature](nereids)support create function command in nereids (#45874) 884d615d6ec is described below commit 884d615d6ec20c1a18f871fea3e4fa56a5316f03 Author: starocean999 <li...@selectdb.com> AuthorDate: Thu Feb 13 14:11:31 2025 +0800 [feature](nereids)support create function command in nereids (#45874) --- .../antlr4/org/apache/doris/nereids/DorisParser.g4 | 52 +- .../doris/nereids/parser/LogicalPlanBuilder.java | 165 +++- .../apache/doris/nereids/trees/plans/PlanType.java | 2 + .../plans/commands/CreateFunctionCommand.java | 1032 ++++++++++++++++++++ .../trees/plans/commands/DropFunctionCommand.java | 113 +++ .../plans/commands/info/ColumnDefinition.java | 242 +---- .../plans/commands/info/FunctionArgTypesInfo.java | 66 ++ .../trees/plans/visitor/CommandVisitor.java | 10 + .../org/apache/doris/nereids/types/DataType.java | 243 +++++ .../suites/ddl_p0/test_alias_function.groovy | 4 +- .../sql_functions/test_alias_function.groovy | 6 + 11 files changed, 1628 insertions(+), 307 deletions(-) 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 71bd2999884..5953029f528 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 @@ -200,6 +200,15 @@ supportedCreateStatement | CREATE SQL_BLOCK_RULE (IF NOT EXISTS)? name=identifier properties=propertyClause? #createSqlBlockRule | CREATE ENCRYPTKEY (IF NOT EXISTS)? multipartIdentifier AS STRING_LITERAL #createEncryptkey + | CREATE statementScope? + (TABLES | AGGREGATE)? FUNCTION (IF NOT EXISTS)? + functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN + RETURNS returnType=dataType (INTERMEDIATE intermediateType=dataType)? + properties=propertyClause? #createUserDefineFunction + | CREATE statementScope? ALIAS FUNCTION (IF NOT EXISTS)? + functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN + WITH PARAMETER LEFT_PAREN parameters=identifierSeq? RIGHT_PAREN + AS expression #createAliasFunction ; supportedAlterStatement @@ -246,11 +255,12 @@ supportedDropStatement | DROP WORKLOAD POLICY (IF EXISTS)? name=identifierOrText #dropWorkloadPolicy | DROP REPOSITORY name=identifier #dropRepository | DROP (DATABASE | SCHEMA) (IF EXISTS)? name=multipartIdentifier FORCE? #dropDatabase - + | DROP statementScope? FUNCTION (IF EXISTS)? + functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN #dropFunction ; supportedShowStatement - : SHOW (GLOBAL | SESSION | LOCAL)? VARIABLES wildWhere? #showVariables + : SHOW statementScope? VARIABLES wildWhere? #showVariables | SHOW AUTHORS #showAuthors | SHOW CREATE (DATABASE | SCHEMA) name=multipartIdentifier #showCreateDatabase | SHOW BROKER #showBroker @@ -301,7 +311,7 @@ supportedShowStatement | SHOW DATABASE databaseId=INTEGER_VALUE #showDatabaseId | SHOW TABLE tableId=INTEGER_VALUE #showTableId | SHOW TRASH (ON backend=STRING_LITERAL)? #showTrash - | SHOW (GLOBAL | SESSION | LOCAL)? STATUS #showStatus + | SHOW statementScope? STATUS #showStatus | SHOW WHITELIST #showWhitelist | SHOW TABLETS BELONG tabletIds+=INTEGER_VALUE (COMMA tabletIds+=INTEGER_VALUE)* #showTabletsBelong @@ -357,7 +367,7 @@ unsupportedShowStatement | SHOW FULL? TABLES ((FROM | IN) database=multipartIdentifier)? wildWhere? #showTables | SHOW FULL? VIEWS ((FROM | IN) database=multipartIdentifier)? wildWhere? #showViews | SHOW CREATE MATERIALIZED VIEW name=multipartIdentifier #showMaterializedView - | SHOW CREATE (GLOBAL | SESSION | LOCAL)? FUNCTION functionIdentifier + | SHOW CREATE statementScope? FUNCTION functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN ((FROM | IN) database=multipartIdentifier)? #showCreateFunction | SHOW (DATABASES | SCHEMAS) (FROM catalog=identifier)? wildWhere? #showDatabases @@ -699,9 +709,7 @@ fromRollup ; unsupportedDropStatement - : DROP (GLOBAL | SESSION | LOCAL)? FUNCTION (IF EXISTS)? - functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN #dropFunction - | DROP TABLE (IF EXISTS)? name=multipartIdentifier FORCE? #dropTable + : DROP TABLE (IF EXISTS)? name=multipartIdentifier FORCE? #dropTable | DROP VIEW (IF EXISTS)? name=multipartIdentifier #dropView | DROP INDEX (IF EXISTS)? name=identifier ON tableName=multipartIdentifier #dropIndex | DROP RESOURCE (IF EXISTS)? name=identifierOrText #dropResource @@ -759,15 +767,6 @@ analyzeProperties unsupportedCreateStatement : CREATE (DATABASE | SCHEMA) (IF NOT EXISTS)? name=multipartIdentifier properties=propertyClause? #createDatabase - | CREATE (GLOBAL | SESSION | LOCAL)? - (TABLES | AGGREGATE)? FUNCTION (IF NOT EXISTS)? - functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN - RETURNS returnType=dataType (INTERMEDIATE intermediateType=dataType)? - properties=propertyClause? #createUserDefineFunction - | CREATE (GLOBAL | SESSION | LOCAL)? ALIAS FUNCTION (IF NOT EXISTS)? - functionIdentifier LEFT_PAREN functionArguments? RIGHT_PAREN - WITH PARAMETER LEFT_PAREN parameters=identifierSeq? RIGHT_PAREN - AS expression #createAliasFunction | CREATE USER (IF NOT EXISTS)? grantUserIdentify (SUPERUSER | DEFAULT ROLE role=STRING_LITERAL)? passwordOption (COMMENT STRING_LITERAL)? #createUser @@ -825,12 +824,13 @@ passwordOption ; functionArguments - : functionArgument (COMMA functionArgument)* + : DOTDOTDOT + | dataTypeList + | dataTypeList COMMA DOTDOTDOT ; -functionArgument - : DOTDOTDOT - | dataType +dataTypeList + : dataType (COMMA dataType)* ; supportedSetStatement @@ -838,7 +838,7 @@ supportedSetStatement (COMMA (optionWithType | optionWithoutType))* #setOptions | SET identifier AS DEFAULT STORAGE VAULT #setDefaultStorageVault | SET PROPERTY (FOR user=identifierOrText)? propertyItemList #setUserProperties - | SET (GLOBAL | LOCAL | SESSION)? TRANSACTION + | SET statementScope? TRANSACTION ( transactionAccessMode | isolationLevel | transactionAccessMode COMMA isolationLevel @@ -846,7 +846,7 @@ supportedSetStatement ; optionWithType - : (GLOBAL | LOCAL | SESSION) identifier EQ (expression | DEFAULT) #setVariableWithType + : statementScope identifier EQ (expression | DEFAULT) #setVariableWithType ; optionWithoutType @@ -862,7 +862,7 @@ optionWithoutType ; variable - : (DOUBLEATSIGN ((GLOBAL | LOCAL | SESSION) DOT)?)? identifier EQ (expression | DEFAULT) #setSystemVariable + : (DOUBLEATSIGN (statementScope DOT)?)? identifier EQ (expression | DEFAULT) #setSystemVariable | ATSIGN identifier EQ expression #setUserVariable ; @@ -875,7 +875,7 @@ isolationLevel ; supportedUnsetStatement - : UNSET (GLOBAL | SESSION | LOCAL)? VARIABLE (ALL | identifier) + : UNSET statementScope? VARIABLE (ALL | identifier) | UNSET DEFAULT STORAGE VAULT ; @@ -968,6 +968,10 @@ dataDesc ; // -----------------Command accessories----------------- +statementScope + : (GLOBAL | SESSION | LOCAL) + ; + buildMode : BUILD (IMMEDIATE | DEFERRED) ; diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index c4c87f05bc3..46cf314b329 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -24,6 +24,7 @@ import org.apache.doris.analysis.ColumnNullableType; import org.apache.doris.analysis.ColumnPosition; import org.apache.doris.analysis.DbName; import org.apache.doris.analysis.EncryptKeyName; +import org.apache.doris.analysis.FunctionName; import org.apache.doris.analysis.PassVar; import org.apache.doris.analysis.SetType; import org.apache.doris.analysis.StorageBackend; @@ -114,6 +115,7 @@ import org.apache.doris.nereids.DorisParser.ComplexColTypeContext; import org.apache.doris.nereids.DorisParser.ComplexColTypeListContext; import org.apache.doris.nereids.DorisParser.ComplexDataTypeContext; import org.apache.doris.nereids.DorisParser.ConstantContext; +import org.apache.doris.nereids.DorisParser.CreateAliasFunctionContext; import org.apache.doris.nereids.DorisParser.CreateCatalogContext; import org.apache.doris.nereids.DorisParser.CreateEncryptkeyContext; import org.apache.doris.nereids.DorisParser.CreateFileContext; @@ -125,9 +127,11 @@ import org.apache.doris.nereids.DorisParser.CreateRowPolicyContext; import org.apache.doris.nereids.DorisParser.CreateSqlBlockRuleContext; import org.apache.doris.nereids.DorisParser.CreateTableContext; import org.apache.doris.nereids.DorisParser.CreateTableLikeContext; +import org.apache.doris.nereids.DorisParser.CreateUserDefineFunctionContext; import org.apache.doris.nereids.DorisParser.CreateViewContext; import org.apache.doris.nereids.DorisParser.CreateWorkloadGroupContext; import org.apache.doris.nereids.DorisParser.CteContext; +import org.apache.doris.nereids.DorisParser.DataTypeListContext; import org.apache.doris.nereids.DorisParser.DataTypeWithNullableContext; import org.apache.doris.nereids.DorisParser.DecimalLiteralContext; import org.apache.doris.nereids.DorisParser.DeleteContext; @@ -139,6 +143,7 @@ import org.apache.doris.nereids.DorisParser.DropConstraintContext; import org.apache.doris.nereids.DorisParser.DropDatabaseContext; import org.apache.doris.nereids.DorisParser.DropEncryptkeyContext; import org.apache.doris.nereids.DorisParser.DropFileContext; +import org.apache.doris.nereids.DorisParser.DropFunctionContext; import org.apache.doris.nereids.DorisParser.DropIndexClauseContext; import org.apache.doris.nereids.DorisParser.DropMTMVContext; import org.apache.doris.nereids.DorisParser.DropPartitionClauseContext; @@ -160,6 +165,8 @@ import org.apache.doris.nereids.DorisParser.ExplainContext; import org.apache.doris.nereids.DorisParser.ExportContext; import org.apache.doris.nereids.DorisParser.FixedPartitionDefContext; import org.apache.doris.nereids.DorisParser.FromClauseContext; +import org.apache.doris.nereids.DorisParser.FunctionArgumentsContext; +import org.apache.doris.nereids.DorisParser.FunctionIdentifierContext; import org.apache.doris.nereids.DorisParser.GroupingElementContext; import org.apache.doris.nereids.DorisParser.GroupingSetContext; import org.apache.doris.nereids.DorisParser.HavingClauseContext; @@ -341,6 +348,7 @@ import org.apache.doris.nereids.DorisParser.SortItemContext; import org.apache.doris.nereids.DorisParser.SpecifiedPartitionContext; import org.apache.doris.nereids.DorisParser.StarContext; import org.apache.doris.nereids.DorisParser.StatementDefaultContext; +import org.apache.doris.nereids.DorisParser.StatementScopeContext; import org.apache.doris.nereids.DorisParser.StepPartitionDefContext; import org.apache.doris.nereids.DorisParser.StringLiteralContext; import org.apache.doris.nereids.DorisParser.StructLiteralContext; @@ -518,6 +526,7 @@ import org.apache.doris.nereids.trees.plans.commands.Constraint; import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand; import org.apache.doris.nereids.trees.plans.commands.CreateEncryptkeyCommand; import org.apache.doris.nereids.trees.plans.commands.CreateFileCommand; +import org.apache.doris.nereids.trees.plans.commands.CreateFunctionCommand; import org.apache.doris.nereids.trees.plans.commands.CreateJobCommand; import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreateMaterializedViewCommand; @@ -538,6 +547,7 @@ import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.DropDatabaseCommand; import org.apache.doris.nereids.trees.plans.commands.DropEncryptkeyCommand; import org.apache.doris.nereids.trees.plans.commands.DropFileCommand; +import org.apache.doris.nereids.trees.plans.commands.DropFunctionCommand; import org.apache.doris.nereids.trees.plans.commands.DropJobCommand; import org.apache.doris.nereids.trees.plans.commands.DropMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.DropProcedureCommand; @@ -669,6 +679,7 @@ import org.apache.doris.nereids.trees.plans.commands.info.DropRollupOp; import org.apache.doris.nereids.trees.plans.commands.info.EnableFeatureOp; import org.apache.doris.nereids.trees.plans.commands.info.FixedRangePartition; import org.apache.doris.nereids.trees.plans.commands.info.FuncNameInfo; +import org.apache.doris.nereids.trees.plans.commands.info.FunctionArgTypesInfo; import org.apache.doris.nereids.trees.plans.commands.info.GeneratedColumnDesc; import org.apache.doris.nereids.trees.plans.commands.info.InPartition; import org.apache.doris.nereids.trees.plans.commands.info.IndexDefinition; @@ -4139,16 +4150,11 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { if (ctx.DEFAULT() != null && ctx.STORAGE() != null && ctx.VAULT() != null) { return new UnsetDefaultStorageVaultCommand(); } - SetType type = SetType.DEFAULT; - if (ctx.GLOBAL() != null) { - type = SetType.GLOBAL; - } else if (ctx.LOCAL() != null || ctx.SESSION() != null) { - type = SetType.SESSION; - } + SetType statementScope = visitStatementScope(ctx.statementScope()); if (ctx.ALL() != null) { - return new UnsetVariableCommand(type, true); + return new UnsetVariableCommand(statementScope, true); } else if (ctx.identifier() != null) { - return new UnsetVariableCommand(type, ctx.identifier().getText()); + return new UnsetVariableCommand(statementScope, ctx.identifier().getText()); } throw new AnalysisException("Should add 'ALL' or variable name"); } @@ -4170,6 +4176,94 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { return new CreateTableLikeCommand(info); } + @Override + public Command visitCreateUserDefineFunction(CreateUserDefineFunctionContext ctx) { + SetType statementScope = visitStatementScope(ctx.statementScope()); + boolean ifNotExists = ctx.EXISTS() != null; + boolean isAggFunction = ctx.AGGREGATE() != null; + boolean isTableFunction = ctx.TABLES() != null; + FunctionName function = visitFunctionIdentifier(ctx.functionIdentifier()); + FunctionArgTypesInfo functionArgTypesInfo; + if (ctx.functionArguments() != null) { + functionArgTypesInfo = visitFunctionArguments(ctx.functionArguments()); + } else { + functionArgTypesInfo = new FunctionArgTypesInfo(new ArrayList<>(), false); + } + DataType returnType = typedVisit(ctx.returnType); + returnType = returnType.conversion(); + DataType intermediateType = ctx.intermediateType != null ? typedVisit(ctx.intermediateType) : null; + if (intermediateType != null) { + intermediateType = intermediateType.conversion(); + } + Map<String, String> properties = ctx.propertyClause() != null + ? Maps.newHashMap(visitPropertyClause(ctx.propertyClause())) + : Maps.newHashMap(); + return new CreateFunctionCommand(statementScope, ifNotExists, isAggFunction, false, isTableFunction, + function, functionArgTypesInfo, returnType, intermediateType, + null, null, properties); + } + + @Override + public Command visitCreateAliasFunction(CreateAliasFunctionContext ctx) { + SetType statementScope = visitStatementScope(ctx.statementScope()); + boolean ifNotExists = ctx.EXISTS() != null; + FunctionName function = visitFunctionIdentifier(ctx.functionIdentifier()); + FunctionArgTypesInfo functionArgTypesInfo; + if (ctx.functionArguments() != null) { + functionArgTypesInfo = visitFunctionArguments(ctx.functionArguments()); + } else { + functionArgTypesInfo = new FunctionArgTypesInfo(new ArrayList<>(), false); + } + List<String> parameters = ctx.parameters != null ? visitIdentifierSeq(ctx.parameters) : new ArrayList<>(); + Expression originFunction = getExpression(ctx.expression()); + return new CreateFunctionCommand(statementScope, ifNotExists, false, true, false, + function, functionArgTypesInfo, VarcharType.MAX_VARCHAR_TYPE, null, + parameters, originFunction, null); + } + + @Override + public Command visitDropFunction(DropFunctionContext ctx) { + SetType statementScope = visitStatementScope(ctx.statementScope()); + boolean ifExists = ctx.EXISTS() != null; + FunctionName function = visitFunctionIdentifier(ctx.functionIdentifier()); + FunctionArgTypesInfo functionArgTypesInfo; + if (ctx.functionArguments() != null) { + functionArgTypesInfo = visitFunctionArguments(ctx.functionArguments()); + } else { + functionArgTypesInfo = new FunctionArgTypesInfo(new ArrayList<>(), false); + } + return new DropFunctionCommand(statementScope, ifExists, function, functionArgTypesInfo); + } + + @Override + public FunctionArgTypesInfo visitFunctionArguments(FunctionArgumentsContext ctx) { + boolean isVariadic = ctx.DOTDOTDOT() != null; + List<DataType> argTypeDefs; + if (ctx.dataTypeList() != null) { + argTypeDefs = visitDataTypeList(ctx.dataTypeList()); + } else { + argTypeDefs = new ArrayList<>(); + } + return new FunctionArgTypesInfo(argTypeDefs, isVariadic); + } + + @Override + public FunctionName visitFunctionIdentifier(FunctionIdentifierContext ctx) { + String functionName = ctx.functionNameIdentifier().getText(); + String dbName = ctx.dbName != null ? ctx.dbName.getText() : null; + return new FunctionName(dbName, functionName); + } + + @Override + public List<DataType> visitDataTypeList(DataTypeListContext ctx) { + List<DataType> dataTypeList = new ArrayList<>(ctx.getChildCount()); + for (DorisParser.DataTypeContext dataTypeContext : ctx.dataType()) { + DataType dataType = typedVisit(dataTypeContext); + dataTypeList.add(dataType.conversion()); + } + return dataTypeList; + } + @Override public LogicalPlan visitShowAuthors(ShowAuthorsContext ctx) { return new ShowAuthorsCommand(); @@ -4213,28 +4307,18 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { @Override public SetVarOp visitSetSystemVariable(SetSystemVariableContext ctx) { - SetType type = SetType.DEFAULT; - if (ctx.GLOBAL() != null) { - type = SetType.GLOBAL; - } else if (ctx.LOCAL() != null || ctx.SESSION() != null) { - type = SetType.SESSION; - } + SetType statementScope = visitStatementScope(ctx.statementScope()); String name = stripQuotes(ctx.identifier().getText()); Expression expression = ctx.expression() != null ? typedVisit(ctx.expression()) : null; - return new SetSessionVarOp(type, name, expression); + return new SetSessionVarOp(statementScope, name, expression); } @Override public SetVarOp visitSetVariableWithType(SetVariableWithTypeContext ctx) { - SetType type = SetType.DEFAULT; - if (ctx.GLOBAL() != null) { - type = SetType.GLOBAL; - } else if (ctx.LOCAL() != null || ctx.SESSION() != null) { - type = SetType.SESSION; - } + SetType statementScope = visitStatementScope(ctx.statementScope()); String name = stripQuotes(ctx.identifier().getText()); Expression expression = ctx.expression() != null ? typedVisit(ctx.expression()) : null; - return new SetSessionVarOp(type, name, expression); + return new SetSessionVarOp(statementScope, name, expression); } @Override @@ -4714,15 +4798,11 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { @Override public LogicalPlan visitShowVariables(ShowVariablesContext ctx) { - SetType type = SetType.DEFAULT; - if (ctx.GLOBAL() != null) { - type = SetType.GLOBAL; - } else if (ctx.LOCAL() != null || ctx.SESSION() != null) { - type = SetType.SESSION; - } + SetType statementScope = visitStatementScope(ctx.statementScope()); if (ctx.wildWhere() != null) { if (ctx.wildWhere().LIKE() != null) { - return new ShowVariablesCommand(type, stripQuotes(ctx.wildWhere().STRING_LITERAL().getText())); + return new ShowVariablesCommand(statementScope, + stripQuotes(ctx.wildWhere().STRING_LITERAL().getText())); } else { StringBuilder sb = new StringBuilder(); sb.append("SELECT `VARIABLE_NAME` AS `Variable_name`, `VARIABLE_VALUE` AS `Value` FROM "); @@ -4730,7 +4810,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { sb.append("."); sb.append("`").append(InfoSchemaDb.DATABASE_NAME).append("`"); sb.append("."); - if (type == SetType.GLOBAL) { + if (statementScope == SetType.GLOBAL) { sb.append("`global_variables` "); } else { sb.append("`session_variables` "); @@ -4739,7 +4819,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { return new NereidsParser().parseSingle(sb.toString()); } } else { - return new ShowVariablesCommand(type, null); + return new ShowVariablesCommand(statementScope, null); } } @@ -5400,15 +5480,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { @Override public LogicalPlan visitShowStatus(ShowStatusContext ctx) { - String scope = null; - if (ctx.GLOBAL() != null) { - scope = "GLOBAL"; - } else if (ctx.SESSION() != null) { - scope = "SESSION"; - } else if (ctx.LOCAL() != null) { - scope = "LOCAL"; - } - + String scope = visitStatementScope(ctx.statementScope()).name(); return new ShowStatusCommand(scope); } @@ -5432,6 +5504,19 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { return new ShowTableCreationCommand(dbName, wild); } + @Override + public SetType visitStatementScope(StatementScopeContext ctx) { + SetType statementScope = SetType.DEFAULT; + if (ctx != null) { + if (ctx.GLOBAL() != null) { + statementScope = SetType.GLOBAL; + } else if (ctx.LOCAL() != null || ctx.SESSION() != null) { + statementScope = SetType.SESSION; + } + } + return statementScope; + } + @Override public LogicalPlan visitAdminShowTabletStorageFormat(AdminShowTabletStorageFormatContext ctx) { return new ShowTabletStorageFormatCommand(ctx.VERBOSE() != null); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java index c435f069d35..1c2b1fcec9e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java @@ -146,6 +146,8 @@ public enum PlanType { LOAD_COMMAND, SELECT_INTO_OUTFILE_COMMAND, UPDATE_COMMAND, + CREATE_FUNCTION_COMMAND, + DROP_FUNCTION_COMMAND, CREATE_MTMV_COMMAND, CREATE_MATERIALIZED_VIEW_COMMAND, CREATE_JOB_COMMAND, diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java new file mode 100644 index 00000000000..8be22c7330d --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java @@ -0,0 +1,1032 @@ +// 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.plans.commands; + +import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.FunctionCallExpr; +import org.apache.doris.analysis.FunctionName; +import org.apache.doris.analysis.FunctionParams; +import org.apache.doris.analysis.SetType; +import org.apache.doris.analysis.SlotRef; +import org.apache.doris.analysis.StmtType; +import org.apache.doris.catalog.AggregateFunction; +import org.apache.doris.catalog.AliasFunction; +import org.apache.doris.catalog.ArrayType; +import org.apache.doris.catalog.Database; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.Function; +import org.apache.doris.catalog.Function.NullableMode; +import org.apache.doris.catalog.FunctionUtil; +import org.apache.doris.catalog.MapType; +import org.apache.doris.catalog.ScalarFunction; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.StructType; +import org.apache.doris.catalog.Type; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.Config; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.FeConstants; +import org.apache.doris.common.util.URI; +import org.apache.doris.common.util.Util; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.CascadesContext; +import org.apache.doris.nereids.analyzer.Scope; +import org.apache.doris.nereids.analyzer.UnboundSlot; +import org.apache.doris.nereids.glue.translator.ExpressionTranslator; +import org.apache.doris.nereids.glue.translator.PlanTranslatorContext; +import org.apache.doris.nereids.properties.PhysicalProperties; +import org.apache.doris.nereids.rules.analysis.ExpressionAnalyzer; +import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext; +import org.apache.doris.nereids.trees.expressions.Add; +import org.apache.doris.nereids.trees.expressions.BitAnd; +import org.apache.doris.nereids.trees.expressions.BitNot; +import org.apache.doris.nereids.trees.expressions.BitOr; +import org.apache.doris.nereids.trees.expressions.BitXor; +import org.apache.doris.nereids.trees.expressions.Divide; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.IntegralDivide; +import org.apache.doris.nereids.trees.expressions.Mod; +import org.apache.doris.nereids.trees.expressions.Multiply; +import org.apache.doris.nereids.trees.expressions.SlotReference; +import org.apache.doris.nereids.trees.expressions.Subtract; +import org.apache.doris.nereids.trees.expressions.functions.BoundFunction; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.info.FunctionArgTypesInfo; +import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.nereids.types.DataType; +import org.apache.doris.proto.FunctionService; +import org.apache.doris.proto.PFunctionServiceGrpc; +import org.apache.doris.proto.Types; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; +import org.apache.doris.thrift.TFunctionBinaryType; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import io.grpc.ManagedChannel; +import io.grpc.netty.NettyChannelBuilder; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.collections.map.CaseInsensitiveMap; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * create a alias or user defined function + */ +public class CreateFunctionCommand extends Command implements ForwardWithSync { + @Deprecated + private static final String OBJECT_FILE_KEY = "object_file"; + private static final String FILE_KEY = "file"; + private static final String SYMBOL_KEY = "symbol"; + private static final String PREPARE_SYMBOL_KEY = "prepare_fn"; + private static final String CLOSE_SYMBOL_KEY = "close_fn"; + private static final String MD5_CHECKSUM = "md5"; + private static final String INIT_KEY = "init_fn"; + private static final String UPDATE_KEY = "update_fn"; + private static final String MERGE_KEY = "merge_fn"; + private static final String SERIALIZE_KEY = "serialize_fn"; + private static final String FINALIZE_KEY = "finalize_fn"; + private static final String GET_VALUE_KEY = "get_value_fn"; + private static final String REMOVE_KEY = "remove_fn"; + private static final String BINARY_TYPE = "type"; + private static final String EVAL_METHOD_KEY = "evaluate"; + private static final String CREATE_METHOD_NAME = "create"; + private static final String DESTROY_METHOD_NAME = "destroy"; + private static final String ADD_METHOD_NAME = "add"; + private static final String SERIALIZE_METHOD_NAME = "serialize"; + private static final String MERGE_METHOD_NAME = "merge"; + private static final String GETVALUE_METHOD_NAME = "getValue"; + private static final String STATE_CLASS_NAME = "State"; + // add for java udf check return type nullable mode, always_nullable or always_not_nullable + private static final String IS_RETURN_NULL = "always_nullable"; + // iff is static load, BE will be cache the udf class load, so only need load once + private static final String IS_STATIC_LOAD = "static_load"; + private static final String EXPIRATION_TIME = "expiration_time"; + + // timeout for both connection and read. 10 seconds is long enough. + private static final int HTTP_TIMEOUT_MS = 10000; + private final SetType setType; + private final boolean ifNotExists; + private final FunctionName functionName; + private final boolean isAggregate; + private final boolean isAlias; + private final boolean isTableFunction; + private final FunctionArgTypesInfo argsDef; + private final DataType returnType; + private DataType intermediateType; + private final Map<String, String> properties; + private final List<String> parameters; + private final Expression originFunction; + private TFunctionBinaryType binaryType = TFunctionBinaryType.JAVA_UDF; + // needed item set after analyzed + private String userFile; + private Function function; + private String checksum = ""; + private boolean isStaticLoad = false; + private long expirationTime = 360; // default 6 hours = 360 minutes + // now set udf default NullableMode is ALWAYS_NULLABLE + // if not, will core dump when input is not null column, but need return null + // like https://github.com/apache/doris/pull/14002/files + private NullableMode returnNullMode = NullableMode.ALWAYS_NULLABLE; + + /** + * CreateFunctionCommand + */ + public CreateFunctionCommand(SetType setType, boolean ifNotExists, boolean isAggregate, boolean isAlias, + boolean isTableFunction, FunctionName functionName, FunctionArgTypesInfo argsDef, + DataType returnType, DataType intermediateType, List<String> parameters, + Expression originFunction, Map<String, String> properties) { + super(PlanType.CREATE_FUNCTION_COMMAND); + this.setType = setType; + this.ifNotExists = ifNotExists; + this.isAggregate = isAggregate; + this.isAlias = isAlias; + this.isTableFunction = isTableFunction; + this.functionName = functionName; + this.argsDef = argsDef; + this.returnType = returnType; + this.intermediateType = intermediateType; + if (parameters == null) { + this.parameters = ImmutableList.of(); + } else { + this.parameters = ImmutableList.copyOf(parameters); + } + this.originFunction = originFunction; + if (properties == null) { + this.properties = ImmutableSortedMap.of(); + } else { + this.properties = ImmutableSortedMap.copyOf(properties, String.CASE_INSENSITIVE_ORDER); + } + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + analyze(ctx); + if (SetType.GLOBAL.equals(setType)) { + Env.getCurrentEnv().getGlobalFunctionMgr().addFunction(function, ifNotExists); + } else { + String dbName = functionName.getDb(); + if (dbName == null) { + dbName = ctx.getDatabase(); + functionName.setDb(dbName); + } + Database db = Env.getCurrentInternalCatalog().getDbOrDdlException(dbName); + db.addFunction(function, ifNotExists); + if (function.isUDTFunction()) { + // all of the table function in doris will have two function + // one is the noraml, and another is outer, the different of them is deal with + // empty: whether need to insert NULL result value + Function outerFunction = function.clone(); + FunctionName name = outerFunction.getFunctionName(); + name.setFn(name.getFunction() + "_outer"); + db.addFunction(outerFunction, ifNotExists); + } + } + } + + @Override + public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { + return visitor.visitCreateFunctionCommand(this, context); + } + + @Override + public StmtType stmtType() { + return StmtType.CREATE; + } + + private void analyze(ConnectContext ctx) throws Exception { + // https://github.com/apache/doris/issues/17810 + // this error report in P0 test, so we suspect that it is related to concurrency + // add this change to test it. + if (Config.use_fuzzy_session_variable) { + synchronized (CreateFunctionCommand.class) { + analyzeCommon(ctx); + // check + if (isAggregate) { + analyzeUdaf(); + } else if (isAlias) { + analyzeAliasFunction(ctx); + } else if (isTableFunction) { + analyzeUdtf(); + } else { + analyzeUdf(); + } + } + } else { + analyzeCommon(ctx); + // check + if (isAggregate) { + analyzeUdaf(); + } else if (isAlias) { + analyzeAliasFunction(ctx); + } else if (isTableFunction) { + analyzeUdtf(); + } else { + analyzeUdf(); + } + } + } + + private void analyzeCommon(ConnectContext ctx) throws AnalysisException { + // check function name + if (functionName.getDb() == null) { + String db = ctx.getDatabase(); + if (Strings.isNullOrEmpty(db) && setType != SetType.GLOBAL) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); + } + } + + // check operation privilege + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + // check argument + argsDef.analyze(); + + // alias function does not need analyze following params + if (isAlias) { + return; + } + returnType.validateDataType(); + if (intermediateType != null) { + intermediateType.validateDataType(); + } else { + intermediateType = returnType; + } + + String type = properties.getOrDefault(BINARY_TYPE, "JAVA_UDF"); + binaryType = getFunctionBinaryType(type); + if (binaryType == null) { + throw new AnalysisException("unknown function type"); + } + if (type.equals("NATIVE")) { + throw new AnalysisException("do not support 'NATIVE' udf type after doris version 1.2.0," + + "please use JAVA_UDF or RPC instead"); + } + + userFile = properties.getOrDefault(FILE_KEY, properties.get(OBJECT_FILE_KEY)); + if (!Strings.isNullOrEmpty(userFile) && binaryType != TFunctionBinaryType.RPC) { + try { + computeObjectChecksum(); + } catch (IOException | NoSuchAlgorithmException e) { + throw new AnalysisException("cannot to compute object's checksum. err: " + e.getMessage()); + } + String md5sum = properties.get(MD5_CHECKSUM); + if (md5sum != null && !md5sum.equalsIgnoreCase(checksum)) { + throw new AnalysisException("library's checksum is not equal with input, checksum=" + checksum); + } + } + if (binaryType == TFunctionBinaryType.JAVA_UDF) { + FunctionUtil.checkEnableJavaUdf(); + + // always_nullable the default value is true, equal null means true + Boolean isReturnNull = parseBooleanFromProperties(IS_RETURN_NULL); + if (isReturnNull != null && !isReturnNull) { + returnNullMode = NullableMode.ALWAYS_NOT_NULLABLE; + } + // static_load the default value is false, equal null means false + Boolean staticLoad = parseBooleanFromProperties(IS_STATIC_LOAD); + if (staticLoad != null && staticLoad) { + isStaticLoad = true; + } + String expirationTimeString = properties.get(EXPIRATION_TIME); + if (expirationTimeString != null) { + long timeMinutes = 0; + try { + timeMinutes = Long.parseLong(expirationTimeString); + } catch (NumberFormatException e) { + throw new AnalysisException(e.getMessage()); + } + if (timeMinutes <= 0) { + throw new AnalysisException("expirationTime should greater than zero: "); + } + this.expirationTime = timeMinutes; + } + } + } + + private Boolean parseBooleanFromProperties(String propertyString) throws AnalysisException { + String valueOfString = properties.get(propertyString); + if (valueOfString == null) { + return null; + } + if (!valueOfString.equalsIgnoreCase("false") && !valueOfString.equalsIgnoreCase("true")) { + throw new AnalysisException(propertyString + " in properties, you should set it false or true"); + } + return Boolean.parseBoolean(valueOfString); + } + + private void computeObjectChecksum() throws IOException, NoSuchAlgorithmException { + if (FeConstants.runningUnitTest) { + // skip checking checksum when running ut + return; + } + + try (InputStream inputStream = Util.getInputStreamFromUrl(userFile, null, HTTP_TIMEOUT_MS, HTTP_TIMEOUT_MS)) { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] buf = new byte[4096]; + int bytesRead = 0; + do { + bytesRead = inputStream.read(buf); + if (bytesRead < 0) { + break; + } + digest.update(buf, 0, bytesRead); + } while (true); + + checksum = Hex.encodeHexString(digest.digest()); + } + } + + private void analyzeUdtf() throws AnalysisException { + String symbol = properties.get(SYMBOL_KEY); + if (Strings.isNullOrEmpty(symbol)) { + throw new AnalysisException("No 'symbol' in properties"); + } + if (!returnType.isArrayType()) { + throw new AnalysisException("JAVA_UDF OF UDTF return type must be array type"); + } + analyzeJavaUdf(symbol); + URI location; + if (!Strings.isNullOrEmpty(userFile)) { + location = URI.create(userFile); + } else { + location = null; + } + function = ScalarFunction.createUdf(binaryType, + functionName, argsDef.getArgTypes(), + ((ArrayType) (returnType.toCatalogDataType())).getItemType(), argsDef.isVariadic(), + location, symbol, null, null); + function.setChecksum(checksum); + function.setNullableMode(returnNullMode); + function.setUDTFunction(true); + // Todo: maybe in create tables function, need register two function, one is + // normal and one is outer as those have different result when result is NULL. + } + + private void analyzeUdaf() throws AnalysisException { + AggregateFunction.AggregateFunctionBuilder builder = AggregateFunction.AggregateFunctionBuilder + .createUdfBuilder(); + URI location; + if (!Strings.isNullOrEmpty(userFile)) { + location = URI.create(userFile); + } else { + location = null; + } + builder.name(functionName).argsType(argsDef.getArgTypes()).retType(returnType.toCatalogDataType()) + .hasVarArgs(argsDef.isVariadic()).intermediateType(intermediateType.toCatalogDataType()) + .location(location); + String initFnSymbol = properties.get(INIT_KEY); + if (initFnSymbol == null && !(binaryType == TFunctionBinaryType.JAVA_UDF + || binaryType == TFunctionBinaryType.RPC)) { + throw new AnalysisException("No 'init_fn' in properties"); + } + String updateFnSymbol = properties.get(UPDATE_KEY); + if (updateFnSymbol == null && !(binaryType == TFunctionBinaryType.JAVA_UDF)) { + throw new AnalysisException("No 'update_fn' in properties"); + } + String mergeFnSymbol = properties.get(MERGE_KEY); + if (mergeFnSymbol == null && !(binaryType == TFunctionBinaryType.JAVA_UDF)) { + throw new AnalysisException("No 'merge_fn' in properties"); + } + String serializeFnSymbol = properties.get(SERIALIZE_KEY); + String finalizeFnSymbol = properties.get(FINALIZE_KEY); + String getValueFnSymbol = properties.get(GET_VALUE_KEY); + String removeFnSymbol = properties.get(REMOVE_KEY); + String symbol = properties.get(SYMBOL_KEY); + if (binaryType == TFunctionBinaryType.RPC && !userFile.contains("://")) { + if (initFnSymbol != null) { + checkRPCUdf(initFnSymbol); + } + checkRPCUdf(updateFnSymbol); + checkRPCUdf(mergeFnSymbol); + if (serializeFnSymbol != null) { + checkRPCUdf(serializeFnSymbol); + } + if (finalizeFnSymbol != null) { + checkRPCUdf(finalizeFnSymbol); + } + if (getValueFnSymbol != null) { + checkRPCUdf(getValueFnSymbol); + } + if (removeFnSymbol != null) { + checkRPCUdf(removeFnSymbol); + } + } else if (binaryType == TFunctionBinaryType.JAVA_UDF) { + if (Strings.isNullOrEmpty(symbol)) { + throw new AnalysisException("No 'symbol' in properties of java-udaf"); + } + analyzeJavaUdaf(symbol); + } + function = builder.initFnSymbol(initFnSymbol).updateFnSymbol(updateFnSymbol).mergeFnSymbol(mergeFnSymbol) + .serializeFnSymbol(serializeFnSymbol).finalizeFnSymbol(finalizeFnSymbol) + .getValueFnSymbol(getValueFnSymbol).removeFnSymbol(removeFnSymbol).symbolName(symbol).build(); + function.setLocation(location); + function.setBinaryType(binaryType); + function.setChecksum(checksum); + function.setNullableMode(returnNullMode); + } + + private void analyzeUdf() throws AnalysisException { + String symbol = properties.get(SYMBOL_KEY); + if (Strings.isNullOrEmpty(symbol)) { + throw new AnalysisException("No 'symbol' in properties"); + } + String prepareFnSymbol = properties.get(PREPARE_SYMBOL_KEY); + String closeFnSymbol = properties.get(CLOSE_SYMBOL_KEY); + // TODO(yangzhg) support check function in FE when function service behind load balancer + // the format for load balance can ref https://github.com/apache/incubator-brpc/blob/master/docs/en/client.md#connect-to-a-cluster + if (binaryType == TFunctionBinaryType.RPC && !userFile.contains("://")) { + if (StringUtils.isNotBlank(prepareFnSymbol) || StringUtils.isNotBlank(closeFnSymbol)) { + throw new AnalysisException("prepare and close in RPC UDF are not supported."); + } + checkRPCUdf(symbol); + } else if (binaryType == TFunctionBinaryType.JAVA_UDF) { + analyzeJavaUdf(symbol); + } + URI location; + if (!Strings.isNullOrEmpty(userFile)) { + location = URI.create(userFile); + } else { + location = null; + } + function = ScalarFunction.createUdf(binaryType, + functionName, argsDef.getArgTypes(), + returnType.toCatalogDataType(), argsDef.isVariadic(), + location, symbol, prepareFnSymbol, closeFnSymbol); + function.setChecksum(checksum); + function.setNullableMode(returnNullMode); + function.setStaticLoad(isStaticLoad); + function.setExpirationTime(expirationTime); + } + + private void analyzeJavaUdaf(String clazz) throws AnalysisException { + HashMap<String, Method> allMethods = new HashMap<>(); + + try { + if (Strings.isNullOrEmpty(userFile)) { + try { + ClassLoader cl = this.getClass().getClassLoader(); + checkUdafClass(clazz, cl, allMethods); + return; + } catch (ClassNotFoundException e) { + throw new AnalysisException("Class [" + clazz + "] not found in classpath"); + } + } + URL[] urls = { new URL("jar:" + userFile + "!/") }; + try (URLClassLoader cl = URLClassLoader.newInstance(urls)) { + checkUdafClass(clazz, cl, allMethods); + } catch (ClassNotFoundException e) { + throw new AnalysisException( + "Class [" + clazz + "] or inner class [State] not found in file :" + userFile); + } catch (IOException e) { + throw new AnalysisException("Failed to load file: " + userFile); + } + } catch (MalformedURLException e) { + throw new AnalysisException("Failed to load file: " + userFile); + } + } + + private void checkUdafClass(String clazz, ClassLoader cl, HashMap<String, Method> allMethods) + throws ClassNotFoundException, AnalysisException { + Class udfClass = cl.loadClass(clazz); + String udfClassName = udfClass.getCanonicalName(); + String stateClassName = udfClassName + "$" + STATE_CLASS_NAME; + Class stateClass = cl.loadClass(stateClassName); + + for (Method m : udfClass.getMethods()) { + if (!m.getDeclaringClass().equals(udfClass)) { + continue; + } + String name = m.getName(); + if (allMethods.containsKey(name)) { + throw new AnalysisException( + String.format("UDF class '%s' has multiple methods with name '%s' ", udfClassName, + name)); + } + allMethods.put(name, m); + } + + if (allMethods.get(CREATE_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", CREATE_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(CREATE_METHOD_NAME, allMethods.get(CREATE_METHOD_NAME), udfClassName); + checkArgumentCount(allMethods.get(CREATE_METHOD_NAME), 0, udfClassName); + checkReturnJavaType(udfClassName, allMethods.get(CREATE_METHOD_NAME), stateClass); + } + + if (allMethods.get(DESTROY_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", DESTROY_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(DESTROY_METHOD_NAME, allMethods.get(DESTROY_METHOD_NAME), + udfClassName); + checkArgumentCount(allMethods.get(DESTROY_METHOD_NAME), 1, udfClassName); + checkReturnJavaType(udfClassName, allMethods.get(DESTROY_METHOD_NAME), void.class); + } + + if (allMethods.get(ADD_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", ADD_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(ADD_METHOD_NAME, allMethods.get(ADD_METHOD_NAME), udfClassName); + checkArgumentCount(allMethods.get(ADD_METHOD_NAME), argsDef.getArgTypes().length + 1, udfClassName); + checkReturnJavaType(udfClassName, allMethods.get(ADD_METHOD_NAME), void.class); + for (int i = 0; i < argsDef.getArgTypes().length; i++) { + Parameter p = allMethods.get(ADD_METHOD_NAME).getParameters()[i + 1]; + checkUdfType(udfClass, allMethods.get(ADD_METHOD_NAME), argsDef.getArgTypes()[i], p.getType(), + p.getName()); + } + } + + if (allMethods.get(SERIALIZE_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", SERIALIZE_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(SERIALIZE_METHOD_NAME, allMethods.get(SERIALIZE_METHOD_NAME), + udfClassName); + checkArgumentCount(allMethods.get(SERIALIZE_METHOD_NAME), 2, udfClassName); + checkReturnJavaType(udfClassName, allMethods.get(SERIALIZE_METHOD_NAME), void.class); + } + + if (allMethods.get(MERGE_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", MERGE_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(MERGE_METHOD_NAME, allMethods.get(MERGE_METHOD_NAME), udfClassName); + checkArgumentCount(allMethods.get(MERGE_METHOD_NAME), 2, udfClassName); + checkReturnJavaType(udfClassName, allMethods.get(MERGE_METHOD_NAME), void.class); + } + + if (allMethods.get(GETVALUE_METHOD_NAME) == null) { + throw new AnalysisException( + String.format("No method '%s' in class '%s'!", GETVALUE_METHOD_NAME, udfClassName)); + } else { + checkMethodNonStaticAndPublic(GETVALUE_METHOD_NAME, allMethods.get(GETVALUE_METHOD_NAME), + udfClassName); + checkArgumentCount(allMethods.get(GETVALUE_METHOD_NAME), 1, udfClassName); + checkReturnUdfType(udfClass, allMethods.get(GETVALUE_METHOD_NAME), returnType.toCatalogDataType()); + } + + if (!Modifier.isPublic(stateClass.getModifiers()) || !Modifier.isStatic(stateClass.getModifiers())) { + throw new AnalysisException( + String.format( + "UDAF '%s' should have one public & static 'State' class to Construction data ", + udfClassName)); + } + } + + private void checkMethodNonStaticAndPublic(String methoName, Method method, String udfClassName) + throws AnalysisException { + if (Modifier.isStatic(method.getModifiers())) { + throw new AnalysisException( + String.format("Method '%s' in class '%s' should be non-static", methoName, udfClassName)); + } + if (!Modifier.isPublic(method.getModifiers())) { + throw new AnalysisException( + String.format("Method '%s' in class '%s' should be public", methoName, udfClassName)); + } + } + + private void checkArgumentCount(Method method, int argumentCount, String udfClassName) throws AnalysisException { + if (method.getParameters().length != argumentCount) { + throw new AnalysisException( + String.format("The number of parameters for method '%s' in class '%s' should be %d", + method.getName(), udfClassName, argumentCount)); + } + } + + private void checkReturnJavaType(String udfClassName, Method method, Class expType) throws AnalysisException { + checkJavaType(udfClassName, method, expType, method.getReturnType(), "return"); + } + + private void checkJavaType(String udfClassName, Method method, Class expType, Class ptype, String pname) + throws AnalysisException { + if (!expType.equals(ptype)) { + throw new AnalysisException( + String.format("UDF class '%s' method '%s' parameter %s[%s] expect type %s", udfClassName, + method.getName(), pname, ptype.getCanonicalName(), expType.getCanonicalName())); + } + } + + private void checkReturnUdfType(Class clazz, Method method, Type expType) throws AnalysisException { + checkUdfType(clazz, method, expType, method.getReturnType(), "return"); + } + + private void analyzeJavaUdf(String clazz) throws AnalysisException { + try { + if (Strings.isNullOrEmpty(userFile)) { + try { + ClassLoader cl = this.getClass().getClassLoader(); + checkUdfClass(clazz, cl); + return; + } catch (ClassNotFoundException e) { + throw new AnalysisException("Class [" + clazz + "] not found in classpath"); + } + } + URL[] urls = { new URL("jar:" + userFile + "!/") }; + try (URLClassLoader cl = URLClassLoader.newInstance(urls)) { + checkUdfClass(clazz, cl); + } catch (ClassNotFoundException e) { + throw new AnalysisException("Class [" + clazz + "] not found in file :" + userFile); + } catch (IOException e) { + throw new AnalysisException("Failed to load file: " + userFile); + } + } catch (MalformedURLException e) { + throw new AnalysisException("Failed to load file: " + userFile); + } + } + + private void checkUdfClass(String clazz, ClassLoader cl) throws ClassNotFoundException, AnalysisException { + Class udfClass = cl.loadClass(clazz); + List<Method> evalList = Arrays.stream(udfClass.getMethods()) + .filter(m -> m.getDeclaringClass().equals(udfClass) && EVAL_METHOD_KEY.equals(m.getName())) + .collect(Collectors.toList()); + if (evalList.size() == 0) { + throw new AnalysisException(String.format( + "No method '%s' in class '%s'!", EVAL_METHOD_KEY, udfClass.getCanonicalName())); + } + List<Method> evalNonStaticAndPublicList = evalList.stream() + .filter(m -> !Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers())) + .collect(Collectors.toList()); + if (evalNonStaticAndPublicList.size() == 0) { + throw new AnalysisException( + String.format("Method '%s' in class '%s' should be non-static and public", EVAL_METHOD_KEY, + udfClass.getCanonicalName())); + } + List<Method> evalArgLengthMatchList = evalNonStaticAndPublicList.stream().filter( + m -> m.getParameters().length == argsDef.getArgTypes().length).collect(Collectors.toList()); + if (evalArgLengthMatchList.size() == 0) { + throw new AnalysisException( + String.format("The number of parameters for method '%s' in class '%s' should be %d", + EVAL_METHOD_KEY, udfClass.getCanonicalName(), argsDef.getArgTypes().length)); + } else if (evalArgLengthMatchList.size() == 1) { + Method method = evalArgLengthMatchList.get(0); + checkUdfType(udfClass, method, returnType.toCatalogDataType(), method.getReturnType(), "return"); + for (int i = 0; i < method.getParameters().length; i++) { + Parameter p = method.getParameters()[i]; + checkUdfType(udfClass, method, argsDef.getArgTypes()[i], p.getType(), p.getName()); + } + } else { + // If multiple methods have the same parameters, + // the error message returned cannot be as specific as a single method + boolean hasError = false; + for (Method method : evalArgLengthMatchList) { + try { + checkUdfType(udfClass, method, returnType.toCatalogDataType(), method.getReturnType(), "return"); + for (int i = 0; i < method.getParameters().length; i++) { + Parameter p = method.getParameters()[i]; + checkUdfType(udfClass, method, argsDef.getArgTypes()[i], p.getType(), p.getName()); + } + hasError = false; + break; + } catch (AnalysisException e) { + hasError = true; + } + } + if (hasError) { + throw new AnalysisException(String.format( + "Multi methods '%s' in class '%s' and no one passed parameter matching verification", + EVAL_METHOD_KEY, udfClass.getCanonicalName())); + } + } + } + + private void checkUdfType(Class clazz, Method method, Type expType, Class pType, String pname) + throws AnalysisException { + Set<Class> javaTypes; + if (expType instanceof ScalarType) { + ScalarType scalarType = (ScalarType) expType; + javaTypes = Type.PrimitiveTypeToJavaClassType.get(scalarType.getPrimitiveType()); + } else if (expType instanceof ArrayType) { + ArrayType arrayType = (ArrayType) expType; + javaTypes = Type.PrimitiveTypeToJavaClassType.get(arrayType.getPrimitiveType()); + } else if (expType instanceof MapType) { + MapType mapType = (MapType) expType; + javaTypes = Type.PrimitiveTypeToJavaClassType.get(mapType.getPrimitiveType()); + } else if (expType instanceof StructType) { + StructType structType = (StructType) expType; + javaTypes = Type.PrimitiveTypeToJavaClassType.get(structType.getPrimitiveType()); + } else { + throw new AnalysisException( + String.format("Method '%s' in class '%s' does not support type '%s'", + method.getName(), clazz.getCanonicalName(), expType)); + } + + if (javaTypes == null) { + throw new AnalysisException( + String.format("Method '%s' in class '%s' does not support type '%s'", + method.getName(), clazz.getCanonicalName(), expType.toString())); + } + if (!javaTypes.contains(pType)) { + throw new AnalysisException( + String.format("UDF class '%s' method '%s' %s[%s] type is not supported!", + clazz.getCanonicalName(), method.getName(), pname, pType.getCanonicalName())); + } + } + + private void checkRPCUdf(String symbol) throws AnalysisException { + // TODO(yangzhg) support check function in FE when function service behind load balancer + // the format for load balance can ref https://github.com/apache/incubator-brpc/blob/master/docs/en/client.md#connect-to-a-cluster + String[] url = userFile.split(":"); + if (url.length != 2) { + throw new AnalysisException("function server address invalid."); + } + String host = url[0]; + int port = Integer.valueOf(url[1]); + ManagedChannel channel = NettyChannelBuilder.forAddress(host, port) + .flowControlWindow(Config.grpc_max_message_size_bytes) + .maxInboundMessageSize(Config.grpc_max_message_size_bytes) + .enableRetry().maxRetryAttempts(3) + .usePlaintext().build(); + PFunctionServiceGrpc.PFunctionServiceBlockingStub stub = PFunctionServiceGrpc.newBlockingStub(channel); + FunctionService.PCheckFunctionRequest.Builder builder = FunctionService.PCheckFunctionRequest.newBuilder(); + builder.getFunctionBuilder().setFunctionName(symbol); + for (Type arg : argsDef.getArgTypes()) { + builder.getFunctionBuilder().addInputs(convertToPParameterType(arg)); + } + builder.getFunctionBuilder().setOutput(convertToPParameterType(returnType.toCatalogDataType())); + FunctionService.PCheckFunctionResponse response = stub.checkFn(builder.build()); + if (response == null || !response.hasStatus()) { + throw new AnalysisException("cannot access function server"); + } + if (response.getStatus().getStatusCode() != 0) { + throw new AnalysisException("check function [" + symbol + "] failed: " + response.getStatus()); + } + } + + private Types.PGenericType convertToPParameterType(Type arg) throws AnalysisException { + Types.PGenericType.Builder typeBuilder = Types.PGenericType.newBuilder(); + switch (arg.getPrimitiveType()) { + case INVALID_TYPE: + typeBuilder.setId(Types.PGenericType.TypeId.UNKNOWN); + break; + case BOOLEAN: + typeBuilder.setId(Types.PGenericType.TypeId.BOOLEAN); + break; + case SMALLINT: + typeBuilder.setId(Types.PGenericType.TypeId.INT16); + break; + case TINYINT: + typeBuilder.setId(Types.PGenericType.TypeId.INT8); + break; + case INT: + typeBuilder.setId(Types.PGenericType.TypeId.INT32); + break; + case BIGINT: + typeBuilder.setId(Types.PGenericType.TypeId.INT64); + break; + case FLOAT: + typeBuilder.setId(Types.PGenericType.TypeId.FLOAT); + break; + case DOUBLE: + typeBuilder.setId(Types.PGenericType.TypeId.DOUBLE); + break; + case CHAR: + case VARCHAR: + typeBuilder.setId(Types.PGenericType.TypeId.STRING); + break; + case HLL: + typeBuilder.setId(Types.PGenericType.TypeId.HLL); + break; + case BITMAP: + typeBuilder.setId(Types.PGenericType.TypeId.BITMAP); + break; + case QUANTILE_STATE: + typeBuilder.setId(Types.PGenericType.TypeId.QUANTILE_STATE); + break; + case AGG_STATE: + typeBuilder.setId(Types.PGenericType.TypeId.AGG_STATE); + break; + case DATE: + typeBuilder.setId(Types.PGenericType.TypeId.DATE); + break; + case DATEV2: + typeBuilder.setId(Types.PGenericType.TypeId.DATEV2); + break; + case DATETIME: + case TIME: + typeBuilder.setId(Types.PGenericType.TypeId.DATETIME); + break; + case DATETIMEV2: + case TIMEV2: + typeBuilder.setId(Types.PGenericType.TypeId.DATETIMEV2); + break; + case DECIMALV2: + case DECIMAL128: + typeBuilder.setId(Types.PGenericType.TypeId.DECIMAL128) + .getDecimalTypeBuilder() + .setPrecision(((ScalarType) arg).getScalarPrecision()) + .setScale(((ScalarType) arg).getScalarScale()); + break; + case DECIMAL32: + typeBuilder.setId(Types.PGenericType.TypeId.DECIMAL32) + .getDecimalTypeBuilder() + .setPrecision(((ScalarType) arg).getScalarPrecision()) + .setScale(((ScalarType) arg).getScalarScale()); + break; + case DECIMAL64: + typeBuilder.setId(Types.PGenericType.TypeId.DECIMAL64) + .getDecimalTypeBuilder() + .setPrecision(((ScalarType) arg).getScalarPrecision()) + .setScale(((ScalarType) arg).getScalarScale()); + break; + case LARGEINT: + typeBuilder.setId(Types.PGenericType.TypeId.INT128); + break; + default: + throw new AnalysisException("type " + arg.getPrimitiveType().toString() + " is not supported"); + } + return typeBuilder.build(); + } + + private TFunctionBinaryType getFunctionBinaryType(String type) { + TFunctionBinaryType binaryType = null; + try { + binaryType = TFunctionBinaryType.valueOf(type); + } catch (IllegalArgumentException e) { + // ignore enum Exception + } + return binaryType; + } + + private void analyzeAliasFunction(ConnectContext ctx) throws AnalysisException { + function = AliasFunction.createFunction(functionName, argsDef.getArgTypes(), + Type.VARCHAR, argsDef.isVariadic(), parameters, translateToLegacyExpr(originFunction, ctx)); + } + + /** + * translate to legacy expr, which do not need complex expression and table columns + */ + private Expr translateToLegacyExpr(Expression expression, ConnectContext ctx) throws AnalysisException { + LogicalEmptyRelation plan = new LogicalEmptyRelation( + ConnectContext.get().getStatementContext().getNextRelationId(), new ArrayList<>()); + CascadesContext cascadesContext = CascadesContext.initContext(ctx.getStatementContext(), plan, + PhysicalProperties.ANY); + Map<String, DataType> argTypeMap = new CaseInsensitiveMap(); + List<DataType> argTypes = argsDef.getArgTypeDefs(); + if (!parameters.isEmpty()) { + if (parameters.size() != argTypes.size()) { + throw new AnalysisException(String.format("arguments' size must be same as parameters' size," + + "arguments : %s, parameters : %s", argTypes.size(), parameters.size())); + } + for (int i = 0; i < parameters.size(); ++i) { + argTypeMap.put(parameters.get(i), argTypes.get(i)); + } + } + ExpressionAnalyzer analyzer = new CustomExpressionAnalyzer(cascadesContext, argTypeMap); + expression = analyzer.analyze(expression); + + PlanTranslatorContext translatorContext = new PlanTranslatorContext(cascadesContext); + ExpressionToExpr translator = new ExpressionToExpr(); + return expression.accept(translator, translatorContext); + } + + private static class CustomExpressionAnalyzer extends ExpressionAnalyzer { + private Map<String, DataType> argTypeMap; + + public CustomExpressionAnalyzer(CascadesContext cascadesContext, Map<String, DataType> argTypeMap) { + super(null, new Scope(ImmutableList.of()), cascadesContext, false, false); + this.argTypeMap = argTypeMap; + } + + @Override + public Expression visitUnboundSlot(UnboundSlot unboundSlot, ExpressionRewriteContext context) { + DataType dataType = argTypeMap.get(unboundSlot.getName()); + if (dataType == null) { + throw new org.apache.doris.nereids.exceptions.AnalysisException( + String.format("param %s's datatype is missed", unboundSlot.getName())); + } + return new SlotReference(unboundSlot.getName(), dataType); + } + } + + private static class ExpressionToExpr extends ExpressionTranslator { + @Override + public Expr visitSlotReference(SlotReference slotReference, PlanTranslatorContext context) { + SlotRef slotRef = new SlotRef(slotReference.getDataType().toCatalogDataType(), slotReference.nullable()); + slotRef.setLabel(slotReference.getName()); + slotRef.setCol(slotReference.getName()); + slotRef.setDisableTableName(true); + return slotRef; + } + + @Override + public Expr visitBoundFunction(BoundFunction function, PlanTranslatorContext context) { + return makeFunctionCallExpr(function, function.getName(), function.hasVarArguments(), context); + } + + @Override + public Expr visitAdd(Add add, PlanTranslatorContext context) { + return makeFunctionCallExpr(add, "add", false, context); + } + + @Override + public Expr visitSubtract(Subtract subtract, PlanTranslatorContext context) { + return makeFunctionCallExpr(subtract, "subtract", false, context); + } + + @Override + public Expr visitMultiply(Multiply multiply, PlanTranslatorContext context) { + return makeFunctionCallExpr(multiply, "multiply", false, context); + } + + @Override + public Expr visitDivide(Divide divide, PlanTranslatorContext context) { + return makeFunctionCallExpr(divide, "divide", false, context); + } + + @Override + public Expr visitIntegralDivide(IntegralDivide integralDivide, PlanTranslatorContext context) { + return makeFunctionCallExpr(integralDivide, "integralDivide", false, context); + } + + @Override + public Expr visitMod(Mod mod, PlanTranslatorContext context) { + return makeFunctionCallExpr(mod, "mod", false, context); + } + + @Override + public Expr visitBitAnd(BitAnd bitAnd, PlanTranslatorContext context) { + return makeFunctionCallExpr(bitAnd, "bitAnd", false, context); + } + + @Override + public Expr visitBitOr(BitOr bitOr, PlanTranslatorContext context) { + return makeFunctionCallExpr(bitOr, "bitOr", false, context); + } + + @Override + public Expr visitBitXor(BitXor bitXor, PlanTranslatorContext context) { + return makeFunctionCallExpr(bitXor, "bitXor", false, context); + } + + @Override + public Expr visitBitNot(BitNot bitNot, PlanTranslatorContext context) { + return makeFunctionCallExpr(bitNot, "bitNot", false, context); + } + + private Expr makeFunctionCallExpr(Expression expression, String name, boolean hasVarArguments, + PlanTranslatorContext context) { + List<Expr> arguments = expression.getArguments().stream() + .map(arg -> arg.accept(this, context)) + .collect(Collectors.toList()); + + List<Type> argTypes = expression.getArguments().stream() + .map(Expression::getDataType) + .map(DataType::toCatalogDataType) + .collect(Collectors.toList()); + + NullableMode nullableMode = expression.nullable() + ? NullableMode.ALWAYS_NULLABLE + : NullableMode.ALWAYS_NOT_NULLABLE; + + org.apache.doris.catalog.ScalarFunction catalogFunction = new org.apache.doris.catalog.ScalarFunction( + new FunctionName(name), argTypes, + expression.getDataType().toCatalogDataType(), hasVarArguments, + "", TFunctionBinaryType.BUILTIN, true, true, nullableMode); + + FunctionCallExpr functionCallExpr; + // create catalog FunctionCallExpr without analyze again + functionCallExpr = new FunctionCallExpr(catalogFunction, new FunctionParams(false, arguments)); + functionCallExpr.setNullableFromNereids(expression.nullable()); + return functionCallExpr; + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropFunctionCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropFunctionCommand.java new file mode 100644 index 00000000000..8135e85a74c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropFunctionCommand.java @@ -0,0 +1,113 @@ +// 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.plans.commands; + +import org.apache.doris.analysis.FunctionName; +import org.apache.doris.analysis.SetType; +import org.apache.doris.analysis.StmtType; +import org.apache.doris.catalog.Database; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.FunctionSearchDesc; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.info.FunctionArgTypesInfo; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; +import org.apache.doris.system.Backend; +import org.apache.doris.task.AgentBatchTask; +import org.apache.doris.task.AgentTaskExecutor; +import org.apache.doris.task.CleanUDFCacheTask; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * drop a alias or user defined function + */ +public class DropFunctionCommand extends Command implements ForwardWithSync { + private static final Logger LOG = LogManager.getLogger(DropFunctionCommand.class); + private SetType setType; + private final boolean ifExists; + private final FunctionName functionName; + private final FunctionArgTypesInfo argsDef; + + /** + * DropFunctionCommand + */ + public DropFunctionCommand(SetType setType, boolean ifExists, FunctionName functionName, + FunctionArgTypesInfo argsDef) { + super(PlanType.CREATE_FUNCTION_COMMAND); + this.setType = setType; + this.ifExists = ifExists; + this.functionName = functionName; + this.argsDef = argsDef; + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + // check operation privilege + if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + argsDef.analyze(); + FunctionSearchDesc function = new FunctionSearchDesc(functionName, argsDef.getArgTypes(), argsDef.isVariadic()); + if (SetType.GLOBAL.equals(setType)) { + Env.getCurrentEnv().getGlobalFunctionMgr().dropFunction(function, ifExists); + } else { + String dbName = functionName.getDb(); + if (dbName == null) { + dbName = ctx.getDatabase(); + functionName.setDb(dbName); + } + Database db = Env.getCurrentInternalCatalog().getDbOrDdlException(dbName); + db.dropFunction(function, ifExists); + } + // BE will cache classload, when drop function, BE need clear cache + ImmutableMap<Long, Backend> backendsInfo = Env.getCurrentSystemInfo().getAllBackendsByAllCluster(); + String functionSignature = getSignatureString(); + AgentBatchTask batchTask = new AgentBatchTask(); + for (Backend backend : backendsInfo.values()) { + CleanUDFCacheTask cleanUDFCacheTask = new CleanUDFCacheTask(backend.getId(), functionSignature); + batchTask.addTask(cleanUDFCacheTask); + LOG.info("clean udf cache in be {}, beId {}", backend.getHost(), backend.getId()); + } + AgentTaskExecutor.submit(batchTask); + } + + @Override + public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { + return visitor.visitDropFunctionCommand(this, context); + } + + @Override + public StmtType stmtType() { + return StmtType.DROP; + } + + private String getSignatureString() { + StringBuilder sb = new StringBuilder(); + sb.append(functionName.getFunction()).append("(").append(Joiner.on(", ").join(argsDef.getArgTypes())); + sb.append(")"); + return sb.toString(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java index 375206305e9..2b66ad1e768 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java @@ -23,9 +23,6 @@ import org.apache.doris.analysis.DefaultValueExprDef; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.KeysType; -import org.apache.doris.catalog.PrimitiveType; -import org.apache.doris.catalog.ScalarType; -import org.apache.doris.catalog.Type; import org.apache.doris.common.FeNameFormat; import org.apache.doris.common.util.SqlUtils; import org.apache.doris.nereids.exceptions.AnalysisException; @@ -46,7 +43,6 @@ import org.apache.doris.qe.SessionVariable; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -294,7 +290,7 @@ public class ColumnDefinition { } catch (Exception e) { throw new AnalysisException(e.getMessage(), e); } - validateDataType(type.toCatalogDataType()); + type.validateDataType(); type = updateCharacterTypeLength(type); if (type.isArrayType()) { int depth = 0; @@ -496,242 +492,6 @@ public class ColumnDefinition { validateGeneratedColumnInfo(); } - // from TypeDef.java analyze() - private void validateDataType(Type catalogType) { - if (catalogType.exceedsMaxNestingDepth()) { - throw new AnalysisException( - String.format("Type exceeds the maximum nesting depth of %s:\n%s", - Type.MAX_NESTING_DEPTH, catalogType.toSql())); - } - if (!catalogType.isSupported()) { - throw new AnalysisException("Unsupported data type: " + catalogType.toSql()); - } - - if (catalogType.isScalarType()) { - validateScalarType((ScalarType) catalogType); - } else if (catalogType.isComplexType()) { - // now we not support array / map / struct nesting complex type - if (catalogType.isArrayType()) { - Type itemType = ((org.apache.doris.catalog.ArrayType) catalogType).getItemType(); - if (itemType instanceof ScalarType) { - validateNestedType(catalogType, (ScalarType) itemType); - } - } - if (catalogType.isMapType()) { - org.apache.doris.catalog.MapType mt = - (org.apache.doris.catalog.MapType) catalogType; - if (mt.getKeyType() instanceof ScalarType) { - validateNestedType(catalogType, (ScalarType) mt.getKeyType()); - } - if (mt.getValueType() instanceof ScalarType) { - validateNestedType(catalogType, (ScalarType) mt.getValueType()); - } - } - if (catalogType.isStructType()) { - ArrayList<org.apache.doris.catalog.StructField> fields = - ((org.apache.doris.catalog.StructType) catalogType).getFields(); - Set<String> fieldNames = new HashSet<>(); - for (org.apache.doris.catalog.StructField field : fields) { - Type fieldType = field.getType(); - if (fieldType instanceof ScalarType) { - validateNestedType(catalogType, (ScalarType) fieldType); - if (!fieldNames.add(field.getName())) { - throw new AnalysisException("Duplicate field name " + field.getName() - + " in struct " + catalogType.toSql()); - } - } - } - } - } - } - - private void validateScalarType(ScalarType scalarType) { - PrimitiveType type = scalarType.getPrimitiveType(); - // When string type length is not assigned, it needs to be assigned to 1. - if (scalarType.getPrimitiveType().isStringType() && !scalarType.isLengthSet()) { - if (scalarType.getPrimitiveType() == PrimitiveType.VARCHAR) { - // always set varchar length MAX_VARCHAR_LENGTH - scalarType.setLength(ScalarType.MAX_VARCHAR_LENGTH); - } else if (scalarType.getPrimitiveType() == PrimitiveType.STRING) { - // always set text length MAX_STRING_LENGTH - scalarType.setLength(ScalarType.MAX_STRING_LENGTH); - } else { - scalarType.setLength(1); - } - } - switch (type) { - case CHAR: - case VARCHAR: { - String name; - int maxLen; - if (type == PrimitiveType.VARCHAR) { - name = "VARCHAR"; - maxLen = ScalarType.MAX_VARCHAR_LENGTH; - } else { - name = "CHAR"; - maxLen = ScalarType.MAX_CHAR_LENGTH; - } - int len = scalarType.getLength(); - // len is decided by child, when it is -1. - - if (len <= 0) { - throw new AnalysisException(name + " size must be > 0: " + len); - } - if (scalarType.getLength() > maxLen) { - throw new AnalysisException(name + " size must be <= " + maxLen + ": " + len); - } - break; - } - case DECIMALV2: { - int precision = scalarType.decimalPrecision(); - int scale = scalarType.decimalScale(); - // precision: [1, 27] - if (precision < 1 || precision > ScalarType.MAX_DECIMALV2_PRECISION) { - throw new AnalysisException("Precision of decimal must between 1 and 27." - + " Precision was set to: " + precision + "."); - } - // scale: [0, 9] - if (scale < 0 || scale > ScalarType.MAX_DECIMALV2_SCALE) { - throw new AnalysisException("Scale of decimal must between 0 and 9." - + " Scale was set to: " + scale + "."); - } - if (precision - scale > ScalarType.MAX_DECIMALV2_PRECISION - - ScalarType.MAX_DECIMALV2_SCALE) { - throw new AnalysisException("Invalid decimal type with precision = " + precision - + ", scale = " + scale); - } - // scale < precision - if (scale > precision) { - throw new AnalysisException("Scale of decimal must be smaller than precision." - + " Scale is " + scale + " and precision is " + precision); - } - break; - } - case DECIMAL32: { - int decimal32Precision = scalarType.decimalPrecision(); - int decimal32Scale = scalarType.decimalScale(); - if (decimal32Precision < 1 - || decimal32Precision > ScalarType.MAX_DECIMAL32_PRECISION) { - throw new AnalysisException("Precision of decimal must between 1 and 9." - + " Precision was set to: " + decimal32Precision + "."); - } - // scale >= 0 - if (decimal32Scale < 0) { - throw new AnalysisException("Scale of decimal must not be less than 0." - + " Scale was set to: " + decimal32Scale + "."); - } - // scale < precision - if (decimal32Scale > decimal32Precision) { - throw new AnalysisException( - "Scale of decimal must be smaller than precision." + " Scale is " - + decimal32Scale + " and precision is " + decimal32Precision); - } - break; - } - case DECIMAL64: { - int decimal64Precision = scalarType.decimalPrecision(); - int decimal64Scale = scalarType.decimalScale(); - if (decimal64Precision < 1 - || decimal64Precision > ScalarType.MAX_DECIMAL64_PRECISION) { - throw new AnalysisException("Precision of decimal64 must between 1 and 18." - + " Precision was set to: " + decimal64Precision + "."); - } - // scale >= 0 - if (decimal64Scale < 0) { - throw new AnalysisException("Scale of decimal must not be less than 0." - + " Scale was set to: " + decimal64Scale + "."); - } - // scale < precision - if (decimal64Scale > decimal64Precision) { - throw new AnalysisException( - "Scale of decimal must be smaller than precision." + " Scale is " - + decimal64Scale + " and precision is " + decimal64Precision); - } - break; - } - case DECIMAL128: { - int decimal128Precision = scalarType.decimalPrecision(); - int decimal128Scale = scalarType.decimalScale(); - if (decimal128Precision < 1 - || decimal128Precision > ScalarType.MAX_DECIMAL128_PRECISION) { - throw new AnalysisException("Precision of decimal128 must between 1 and 38." - + " Precision was set to: " + decimal128Precision + "."); - } - // scale >= 0 - if (decimal128Scale < 0) { - throw new AnalysisException("Scale of decimal must not be less than 0." - + " Scale was set to: " + decimal128Scale + "."); - } - // scale < precision - if (decimal128Scale > decimal128Precision) { - throw new AnalysisException( - "Scale of decimal must be smaller than precision." + " Scale is " - + decimal128Scale + " and precision is " + decimal128Precision); - } - break; - } - case DECIMAL256: { - if (SessionVariable.getEnableDecimal256()) { - int precision = scalarType.decimalPrecision(); - int scale = scalarType.decimalScale(); - if (precision < 1 || precision > ScalarType.MAX_DECIMAL256_PRECISION) { - throw new AnalysisException("Precision of decimal256 must between 1 and 76." - + " Precision was set to: " + precision + "."); - } - // scale >= 0 - if (scale < 0) { - throw new AnalysisException("Scale of decimal must not be less than 0." - + " Scale was set to: " + scale + "."); - } - // scale < precision - if (scale > precision) { - throw new AnalysisException( - "Scale of decimal must be smaller than precision." + " Scale is " - + scale + " and precision is " + precision); - } - break; - } else { - int precision = scalarType.decimalPrecision(); - throw new AnalysisException("Column of type Decimal256 with precision " - + precision + " in not supported."); - } - } - case TIMEV2: - case DATETIMEV2: { - int precision = scalarType.decimalPrecision(); - int scale = scalarType.decimalScale(); - // precision: [1, 27] - if (precision != ScalarType.DATETIME_PRECISION) { - throw new AnalysisException( - "Precision of Datetime/Time must be " + ScalarType.DATETIME_PRECISION - + "." + " Precision was set to: " + precision + "."); - } - // scale: [0, 9] - if (scale < 0 || scale > 6) { - throw new AnalysisException("Scale of Datetime/Time must between 0 and 6." - + " Scale was set to: " + scale + "."); - } - break; - } - case INVALID_TYPE: - throw new AnalysisException("Invalid type."); - default: - break; - } - } - - private void validateNestedType(Type parent, Type child) throws AnalysisException { - if (child.isNull()) { - throw new AnalysisException("Unsupported data type: " + child.toSql()); - } - // check whether the sub-type is supported - if (!parent.supportSubType(child)) { - throw new AnalysisException( - parent.getPrimitiveType() + " unsupported sub-type: " + child.toSql()); - } - validateDataType(child); - } - /** * translate to catalog create table stmt */ diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/FunctionArgTypesInfo.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/FunctionArgTypesInfo.java new file mode 100644 index 00000000000..f02c3d1b2ec --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/FunctionArgTypesInfo.java @@ -0,0 +1,66 @@ +// 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.plans.commands.info; + +import org.apache.doris.catalog.Type; +import org.apache.doris.nereids.types.DataType; +import org.apache.doris.nereids.util.Utils; + +import java.util.List; +import java.util.Objects; + +/** + * represent function arguments + */ +public class FunctionArgTypesInfo { + private final List<DataType> argTypeDefs; + private final boolean isVariadic; + + // set after analyze + private Type[] argTypes; + + public FunctionArgTypesInfo(List<DataType> argTypeDefs, boolean isVariadic) { + this.argTypeDefs = Utils.fastToImmutableList(Objects.requireNonNull(argTypeDefs, + "argTypeDefs should not be null")); + this.isVariadic = isVariadic; + } + + public Type[] getArgTypes() { + return argTypes; + } + + public List<DataType> getArgTypeDefs() { + return argTypeDefs; + } + + public boolean isVariadic() { + return isVariadic; + } + + /** + * validate + */ + public void analyze() { + argTypes = new Type[argTypeDefs.size()]; + int i = 0; + for (DataType dataType : argTypeDefs) { + dataType.validateDataType(); + argTypes[i++] = dataType.toCatalogDataType(); + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java index b097ad65ea9..5d8f762e3cb 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java @@ -46,6 +46,7 @@ import org.apache.doris.nereids.trees.plans.commands.Command; import org.apache.doris.nereids.trees.plans.commands.CreateCatalogCommand; import org.apache.doris.nereids.trees.plans.commands.CreateEncryptkeyCommand; import org.apache.doris.nereids.trees.plans.commands.CreateFileCommand; +import org.apache.doris.nereids.trees.plans.commands.CreateFunctionCommand; import org.apache.doris.nereids.trees.plans.commands.CreateJobCommand; import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreateMaterializedViewCommand; @@ -65,6 +66,7 @@ import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.DropDatabaseCommand; import org.apache.doris.nereids.trees.plans.commands.DropEncryptkeyCommand; import org.apache.doris.nereids.trees.plans.commands.DropFileCommand; +import org.apache.doris.nereids.trees.plans.commands.DropFunctionCommand; import org.apache.doris.nereids.trees.plans.commands.DropJobCommand; import org.apache.doris.nereids.trees.plans.commands.DropMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.DropProcedureCommand; @@ -225,6 +227,14 @@ public interface CommandVisitor<R, C> { return visitCommand(createEncryptKeyCommand, context); } + default R visitCreateFunctionCommand(CreateFunctionCommand createFunctionCommand, C context) { + return visitCommand(createFunctionCommand, context); + } + + default R visitDropFunctionCommand(DropFunctionCommand dropFunctionCommand, C context) { + return visitCommand(dropFunctionCommand, context); + } + default R visitCreateTableCommand(CreateTableCommand createTableCommand, C context) { return visitCommand(createTableCommand, 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 9b77017f6de..581edecb85f 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 @@ -33,14 +33,18 @@ import org.apache.doris.nereids.types.coercion.CharacterType; import org.apache.doris.nereids.types.coercion.IntegralType; import org.apache.doris.nereids.types.coercion.NumericType; import org.apache.doris.nereids.types.coercion.PrimitiveType; +import org.apache.doris.qe.SessionVariable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -763,4 +767,243 @@ public abstract class DataType { } return false; } + + public void validateDataType() { + validateCatalogDataType(toCatalogDataType()); + } + + private static void validateCatalogDataType(Type catalogType) { + if (catalogType.exceedsMaxNestingDepth()) { + throw new AnalysisException( + String.format("Type exceeds the maximum nesting depth of %s:\n%s", + Type.MAX_NESTING_DEPTH, catalogType.toSql())); + } + if (!catalogType.isSupported()) { + throw new AnalysisException("Unsupported data type: " + catalogType.toSql()); + } + + if (catalogType.isScalarType()) { + validateScalarType((ScalarType) catalogType); + } else if (catalogType.isComplexType()) { + // now we not support array / map / struct nesting complex type + if (catalogType.isArrayType()) { + Type itemType = ((org.apache.doris.catalog.ArrayType) catalogType).getItemType(); + if (itemType instanceof ScalarType) { + validateNestedType(catalogType, (ScalarType) itemType); + } + } + if (catalogType.isMapType()) { + org.apache.doris.catalog.MapType mt = + (org.apache.doris.catalog.MapType) catalogType; + if (mt.getKeyType() instanceof ScalarType) { + validateNestedType(catalogType, (ScalarType) mt.getKeyType()); + } + if (mt.getValueType() instanceof ScalarType) { + validateNestedType(catalogType, (ScalarType) mt.getValueType()); + } + } + if (catalogType.isStructType()) { + ArrayList<org.apache.doris.catalog.StructField> fields = + ((org.apache.doris.catalog.StructType) catalogType).getFields(); + Set<String> fieldNames = new HashSet<>(); + for (org.apache.doris.catalog.StructField field : fields) { + Type fieldType = field.getType(); + if (fieldType instanceof ScalarType) { + validateNestedType(catalogType, (ScalarType) fieldType); + if (!fieldNames.add(field.getName())) { + throw new AnalysisException("Duplicate field name " + field.getName() + + " in struct " + catalogType.toSql()); + } + } + } + } + } + } + + private static void validateNestedType(Type parent, Type child) throws AnalysisException { + if (child.isNull()) { + throw new AnalysisException("Unsupported data type: " + child.toSql()); + } + // check whether the sub-type is supported + if (!parent.supportSubType(child)) { + throw new AnalysisException( + parent.getPrimitiveType() + " unsupported sub-type: " + child.toSql()); + } + validateCatalogDataType(child); + } + + private static void validateScalarType(ScalarType scalarType) { + org.apache.doris.catalog.PrimitiveType type = scalarType.getPrimitiveType(); + // When string type length is not assigned, it needs to be assigned to 1. + if (scalarType.getPrimitiveType().isStringType() && !scalarType.isLengthSet()) { + if (scalarType.getPrimitiveType() == org.apache.doris.catalog.PrimitiveType.VARCHAR) { + // always set varchar length MAX_VARCHAR_LENGTH + scalarType.setLength(ScalarType.MAX_VARCHAR_LENGTH); + } else if (scalarType.getPrimitiveType() == org.apache.doris.catalog.PrimitiveType.STRING) { + // always set text length MAX_STRING_LENGTH + scalarType.setLength(ScalarType.MAX_STRING_LENGTH); + } else { + scalarType.setLength(1); + } + } + switch (type) { + case CHAR: + case VARCHAR: { + String name; + int maxLen; + if (type == org.apache.doris.catalog.PrimitiveType.VARCHAR) { + name = "VARCHAR"; + maxLen = ScalarType.MAX_VARCHAR_LENGTH; + } else { + name = "CHAR"; + maxLen = ScalarType.MAX_CHAR_LENGTH; + } + int len = scalarType.getLength(); + // len is decided by child, when it is -1. + + if (len <= 0) { + throw new AnalysisException(name + " size must be > 0: " + len); + } + if (scalarType.getLength() > maxLen) { + throw new AnalysisException(name + " size must be <= " + maxLen + ": " + len); + } + break; + } + case DECIMALV2: { + int precision = scalarType.decimalPrecision(); + int scale = scalarType.decimalScale(); + // precision: [1, 27] + if (precision < 1 || precision > ScalarType.MAX_DECIMALV2_PRECISION) { + throw new AnalysisException("Precision of decimal must between 1 and 27." + + " Precision was set to: " + precision + "."); + } + // scale: [0, 9] + if (scale < 0 || scale > ScalarType.MAX_DECIMALV2_SCALE) { + throw new AnalysisException("Scale of decimal must between 0 and 9." + + " Scale was set to: " + scale + "."); + } + if (precision - scale > ScalarType.MAX_DECIMALV2_PRECISION + - ScalarType.MAX_DECIMALV2_SCALE) { + throw new AnalysisException("Invalid decimal type with precision = " + precision + + ", scale = " + scale); + } + // scale < precision + if (scale > precision) { + throw new AnalysisException("Scale of decimal must be smaller than precision." + + " Scale is " + scale + " and precision is " + precision); + } + break; + } + case DECIMAL32: { + int decimal32Precision = scalarType.decimalPrecision(); + int decimal32Scale = scalarType.decimalScale(); + if (decimal32Precision < 1 + || decimal32Precision > ScalarType.MAX_DECIMAL32_PRECISION) { + throw new AnalysisException("Precision of decimal must between 1 and 9." + + " Precision was set to: " + decimal32Precision + "."); + } + // scale >= 0 + if (decimal32Scale < 0) { + throw new AnalysisException("Scale of decimal must not be less than 0." + + " Scale was set to: " + decimal32Scale + "."); + } + // scale < precision + if (decimal32Scale > decimal32Precision) { + throw new AnalysisException( + "Scale of decimal must be smaller than precision." + " Scale is " + + decimal32Scale + " and precision is " + decimal32Precision); + } + break; + } + case DECIMAL64: { + int decimal64Precision = scalarType.decimalPrecision(); + int decimal64Scale = scalarType.decimalScale(); + if (decimal64Precision < 1 + || decimal64Precision > ScalarType.MAX_DECIMAL64_PRECISION) { + throw new AnalysisException("Precision of decimal64 must between 1 and 18." + + " Precision was set to: " + decimal64Precision + "."); + } + // scale >= 0 + if (decimal64Scale < 0) { + throw new AnalysisException("Scale of decimal must not be less than 0." + + " Scale was set to: " + decimal64Scale + "."); + } + // scale < precision + if (decimal64Scale > decimal64Precision) { + throw new AnalysisException( + "Scale of decimal must be smaller than precision." + " Scale is " + + decimal64Scale + " and precision is " + decimal64Precision); + } + break; + } + case DECIMAL128: { + int decimal128Precision = scalarType.decimalPrecision(); + int decimal128Scale = scalarType.decimalScale(); + if (decimal128Precision < 1 + || decimal128Precision > ScalarType.MAX_DECIMAL128_PRECISION) { + throw new AnalysisException("Precision of decimal128 must between 1 and 38." + + " Precision was set to: " + decimal128Precision + "."); + } + // scale >= 0 + if (decimal128Scale < 0) { + throw new AnalysisException("Scale of decimal must not be less than 0." + + " Scale was set to: " + decimal128Scale + "."); + } + // scale < precision + if (decimal128Scale > decimal128Precision) { + throw new AnalysisException( + "Scale of decimal must be smaller than precision." + " Scale is " + + decimal128Scale + " and precision is " + decimal128Precision); + } + break; + } + case DECIMAL256: { + if (SessionVariable.getEnableDecimal256()) { + int precision = scalarType.decimalPrecision(); + int scale = scalarType.decimalScale(); + if (precision < 1 || precision > ScalarType.MAX_DECIMAL256_PRECISION) { + throw new AnalysisException("Precision of decimal256 must between 1 and 76." + + " Precision was set to: " + precision + "."); + } + // scale >= 0 + if (scale < 0) { + throw new AnalysisException("Scale of decimal must not be less than 0." + + " Scale was set to: " + scale + "."); + } + // scale < precision + if (scale > precision) { + throw new AnalysisException( + "Scale of decimal must be smaller than precision." + " Scale is " + + scale + " and precision is " + precision); + } + break; + } else { + int precision = scalarType.decimalPrecision(); + throw new AnalysisException("Column of type Decimal256 with precision " + + precision + " in not supported."); + } + } + case TIMEV2: + case DATETIMEV2: { + int precision = scalarType.decimalPrecision(); + int scale = scalarType.decimalScale(); + // precision: [1, 27] + if (precision != ScalarType.DATETIME_PRECISION) { + throw new AnalysisException( + "Precision of Datetime/Time must be " + ScalarType.DATETIME_PRECISION + + "." + " Precision was set to: " + precision + "."); + } + // scale: [0, 9] + if (scale < 0 || scale > 6) { + throw new AnalysisException("Scale of Datetime/Time must between 0 and 6." + + " Scale was set to: " + scale + "."); + } + break; + } + case INVALID_TYPE: + throw new AnalysisException("Invalid type."); + default: + break; + } + } } diff --git a/regression-test/suites/ddl_p0/test_alias_function.groovy b/regression-test/suites/ddl_p0/test_alias_function.groovy index fa2ced713f8..7793de92553 100644 --- a/regression-test/suites/ddl_p0/test_alias_function.groovy +++ b/regression-test/suites/ddl_p0/test_alias_function.groovy @@ -18,10 +18,10 @@ suite("test_alias_function") { sql """DROP FUNCTION IF EXISTS mesh_udf_test1(INT,INT)""" - sql """CREATE ALIAS FUNCTION IF NOT EXISTS mesh_udf_test1(INT,INT) WITH PARAMETER(n,d) AS ROUND(1+floor(n/d));""" + sql """CREATE ALIAS FUNCTION mesh_udf_test1(INT,INT) WITH PARAMETER(n,d) AS ROUND(1+floor(n/d));""" qt_sql1 """select mesh_udf_test1(1,2);""" sql """DROP FUNCTION IF EXISTS mesh_udf_test2(INT,INT)""" - sql """CREATE ALIAS FUNCTION IF NOT EXISTS mesh_udf_test2(INT,INT) WITH PARAMETER(n,d) AS add(1,floor(divide(n,d)))""" + sql """CREATE ALIAS FUNCTION mesh_udf_test2(INT,INT) WITH PARAMETER(n,d) AS add(1,floor(divide(n,d)))""" qt_sql1 """select mesh_udf_test2(1,2);""" } diff --git a/regression-test/suites/query_p0/sql_functions/test_alias_function.groovy b/regression-test/suites/query_p0/sql_functions/test_alias_function.groovy index 095ec89e220..8b281d6faa0 100644 --- a/regression-test/suites/query_p0/sql_functions/test_alias_function.groovy +++ b/regression-test/suites/query_p0/sql_functions/test_alias_function.groovy @@ -16,6 +16,12 @@ // under the License. suite('test_alias_function', "arrow_flight_sql") { + sql ''' + DROP FUNCTION IF EXISTS f1() + ''' + sql ''' + DROP FUNCTION IF EXISTS f2() + ''' sql ''' CREATE ALIAS FUNCTION IF NOT EXISTS f1(DATETIMEV2(3), INT) with PARAMETER (datetime1, int1) as date_trunc(days_sub(datetime1, int1), 'day')''' --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org