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 5d2698924fc [Enhancement](nereids)support show functions (#49893) 5d2698924fc is described below commit 5d2698924fc0a39db6bf5d18f0d6727d3f9b35ea Author: lsy3993 <110876560+lsy3...@users.noreply.github.com> AuthorDate: Mon Apr 14 14:28:34 2025 +0800 [Enhancement](nereids)support show functions (#49893) --- .../antlr4/org/apache/doris/nereids/DorisParser.g4 | 4 +- .../org/apache/doris/catalog/FunctionRegistry.java | 17 ++ .../doris/nereids/parser/LogicalPlanBuilder.java | 26 +++ .../apache/doris/nereids/trees/plans/PlanType.java | 1 + .../trees/plans/commands/ShowFunctionsCommand.java | 236 +++++++++++++++++++++ .../trees/plans/visitor/CommandVisitor.java | 5 + .../plans/commands/ShowFunctionsCommandTest.java | 190 +++++++++++++++++ .../show/test_nereids_show_functions.groovy | 92 ++++++++ 8 files changed, 569 insertions(+), 2 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 bf480cd099c..77a28ea6f8a 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 @@ -285,6 +285,8 @@ supportedShowStatement | SHOW LAST INSERT #showLastInsert | SHOW ((CHAR SET) | CHARSET) #showCharset | SHOW DELETE ((FROM | IN) database=multipartIdentifier)? #showDelete + | SHOW FULL? BUILTIN? FUNCTIONS + ((FROM | IN) database=multipartIdentifier)? (LIKE STRING_LITERAL)? #showFunctions | SHOW ALL? GRANTS #showGrants | SHOW GRANTS FOR userIdentify #showGrantsForUser | SHOW SYNC JOB ((FROM | IN) database=multipartIdentifier)? #showSyncJob @@ -415,8 +417,6 @@ unsupportedShowStatement wildWhere? sortClause? limitClause? #showPartitions | SHOW RESOURCES wildWhere? sortClause? limitClause? #showResources | SHOW WORKLOAD GROUPS wildWhere? #showWorkloadGroups - | SHOW FULL? BUILTIN? FUNCTIONS - ((FROM | IN) database=multipartIdentifier)? wildWhere? #showFunctions | SHOW GLOBAL FULL? FUNCTIONS wildWhere? #showGlobalFunctions | SHOW TYPECAST ((FROM | IN) database=multipartIdentifier)? #showTypeCast | SHOW (KEY | KEYS | INDEX | INDEXES) diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java index dc11b5d02be..74fe1e38753 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java @@ -74,6 +74,18 @@ public class FunctionRegistry { afterRegisterBuiltinFunctions(name2BuiltinBuilders); } + public Map<String, List<FunctionBuilder>> getName2BuiltinBuilders() { + return name2BuiltinBuilders; + } + + public String getGlobalFunctionDbName() { + return GLOBAL_FUNCTION; + } + + public Map<String, Map<String, List<FunctionBuilder>>> getName2UdfBuilders() { + return name2UdfBuilders; + } + // this function is used to test. // for example, you can create child class of FunctionRegistry and clear builtin functions or add more functions // in this method @@ -284,6 +296,11 @@ public class FunctionRegistry { Map<String, List<FunctionBuilder>> builders = name2UdfBuilders.getOrDefault(dbName, ImmutableMap.of()); builders.getOrDefault(name, Lists.newArrayList()) .removeIf(builder -> ((UdfBuilder) builder).getArgTypes().equals(argTypes)); + + // the name will be used when show functions, so remove the name when it's dropped + if (builders.getOrDefault(name, Lists.newArrayList()).isEmpty()) { + builders.remove(name); + } } } 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 8e8944035f7..f722344fa7f 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 @@ -329,6 +329,7 @@ import org.apache.doris.nereids.DorisParser.ShowDynamicPartitionContext; import org.apache.doris.nereids.DorisParser.ShowEncryptKeysContext; import org.apache.doris.nereids.DorisParser.ShowEventsContext; import org.apache.doris.nereids.DorisParser.ShowFrontendsContext; +import org.apache.doris.nereids.DorisParser.ShowFunctionsContext; import org.apache.doris.nereids.DorisParser.ShowGrantsContext; import org.apache.doris.nereids.DorisParser.ShowGrantsForUserContext; import org.apache.doris.nereids.DorisParser.ShowLastInsertContext; @@ -652,6 +653,7 @@ import org.apache.doris.nereids.trees.plans.commands.ShowDynamicPartitionCommand import org.apache.doris.nereids.trees.plans.commands.ShowEncryptKeysCommand; import org.apache.doris.nereids.trees.plans.commands.ShowEventsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowFrontendsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowFunctionsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowGrantsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowIndexStatsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowLastInsertCommand; @@ -5373,6 +5375,30 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> { return new ShowFrontendsCommand(detail); } + @Override + public LogicalPlan visitShowFunctions(ShowFunctionsContext ctx) { + String dbName = null; + if (ctx.database != null) { + List<String> nameParts = visitMultipartIdentifier(ctx.database); + if (nameParts.size() == 1) { + dbName = nameParts.get(0); + } else if (nameParts.size() == 2) { + dbName = nameParts.get(1); + } else { + throw new AnalysisException("nameParts in analyze database should be [ctl.]db"); + } + } + + boolean isVerbose = ctx.FULL() != null; + boolean isBuiltin = ctx.BUILTIN() != null; + + String wild = null; + if (ctx.STRING_LITERAL() != null) { + wild = stripQuotes(ctx.STRING_LITERAL().getText()); + } + return new ShowFunctionsCommand(dbName, isBuiltin, isVerbose, wild); + } + @Override public LogicalPlan visitShowCreateDatabase(ShowCreateDatabaseContext ctx) { List<String> nameParts = visitMultipartIdentifier(ctx.name); 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 353cfb1788a..9bfdedb0e89 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 @@ -249,6 +249,7 @@ public enum PlanType { SHOW_EVENTS_COMMAND, SHOW_DATA_TYPES_COMMAND, SHOW_FRONTENDS_COMMAND, + SHOW_FUNCTIONS_COMMAND, SHOW_GRANTS_COMMAND, SHOW_INDEX_STATS_COMMAND, SHOW_LAST_INSERT_COMMAND, diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java new file mode 100644 index 00000000000..d5c003052df --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java @@ -0,0 +1,236 @@ +// 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.SetType; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.Database; +import org.apache.doris.catalog.DatabaseIf; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.FunctionRegistry; +import org.apache.doris.catalog.FunctionUtil; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.util.ListComparator; +import org.apache.doris.common.util.OrderByPair; +import org.apache.doris.common.util.Util; +import org.apache.doris.datasource.InternalCatalog; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.ShowResultSet; +import org.apache.doris.qe.ShowResultSetMetaData; +import org.apache.doris.qe.StmtExecutor; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * show functions command + */ +public class ShowFunctionsCommand extends ShowCommand { + private static final ShowResultSetMetaData META_DATA = + ShowResultSetMetaData.builder() + .addColumn(new Column("Signature", ScalarType.STRING)) + .addColumn(new Column("Return Type", ScalarType.STRING)) + .addColumn(new Column("Function Type", ScalarType.STRING)) + .addColumn(new Column("Intermediate Type", ScalarType.STRING)) + .addColumn(new Column("Properties", ScalarType.STRING)) + .build(); + + private String dbName; + private boolean isBuiltin; + private boolean isVerbose; + private String likeCondition; + private SetType type = SetType.DEFAULT; + + /** + * constructor + */ + public ShowFunctionsCommand(String dbName, boolean isBuiltin, boolean isVerbose, String likeCondition) { + super(PlanType.SHOW_FUNCTIONS_COMMAND); + this.dbName = dbName; + this.isBuiltin = isBuiltin; + this.isVerbose = isVerbose; + this.likeCondition = likeCondition; + } + + /*** + * get Info by nereids. + * To make the code in nereids more concise, all irrelevant information here will use an empty string. + */ + private List<Comparable> getInfo(boolean isVerbose, String funcName) { + List<Comparable> row = Lists.newArrayList(); + if (isVerbose) { + // signature + row.add(funcName); + // return type + row.add(""); + // function type + // intermediate type + row.add(""); + row.add(""); + // property + row.add(""); + } else { + row.add(funcName); + } + return row; + } + + @VisibleForTesting + protected boolean like(String funcName, String likeCondition) { + funcName = funcName.toLowerCase(); + return funcName.matches(likeCondition.replace(".", "\\.").replace("?", ".").replace("%", ".*").toLowerCase()); + } + + /*** + * get resultRowSet + */ + @VisibleForTesting + protected List<List<String>> getResultRowSetByFunctions(List<String> functions) { + List<List<String>> resultRowSet = Lists.newArrayList(); + List<List<Comparable>> rowSet = Lists.newArrayList(); + for (String function : functions) { + List<Comparable> row = getInfo(isVerbose, function); + // like predicate + if (likeCondition == null || like(function, likeCondition)) { + rowSet.add(row); + } + } + + // sort function rows by first column asc + ListComparator<List<Comparable>> comparator = null; + OrderByPair orderByPair = new OrderByPair(0, false); + comparator = new ListComparator<>(orderByPair); + Collections.sort(rowSet, comparator); + + Set<String> functionNameSet = new HashSet<>(); + for (List<Comparable> row : rowSet) { + List<String> resultRow = Lists.newArrayList(); + // if not verbose, remove duplicate function name + if (functionNameSet.contains(row.get(0).toString())) { + continue; + } + for (Comparable column : row) { + resultRow.add(column.toString()); + } + resultRowSet.add(resultRow); + functionNameSet.add(resultRow.get(0)); + } + return resultRowSet; + } + + /*** + * get resultRowSet + */ + private List<List<String>> getResultRowSet(ConnectContext ctx) throws AnalysisException { + List<String> functions = getFunctions(ctx); + return getResultRowSetByFunctions(functions); + } + + /*** + * get functions by nereids. + * All functions including builtin and udf are registered in FunctionRegistry + */ + @VisibleForTesting + protected List<String> getFunctions(ConnectContext ctx) throws AnalysisException { + List<String> functions = Lists.newArrayList(); + if (ctx == null || ctx.getEnv() == null || ctx.getEnv().getFunctionRegistry() == null) { + return functions; + } + + FunctionRegistry functionRegistry = ctx.getEnv().getFunctionRegistry(); + Map<String, Map<String, List<FunctionBuilder>>> udfFunctions = functionRegistry.getName2UdfBuilders(); + if (!FunctionUtil.isGlobalFunction(type)) { + Util.prohibitExternalCatalog(ctx.getDefaultCatalog(), this.getClass().getSimpleName()); + DatabaseIf db = ctx.getCurrentCatalog().getDbOrAnalysisException(dbName); + if (db instanceof Database) { + Map<String, List<FunctionBuilder>> builtinFunctions = functionRegistry.getName2BuiltinBuilders(); + functions = isBuiltin ? new ArrayList<>(builtinFunctions.keySet()) : + new ArrayList<>(udfFunctions.getOrDefault(dbName, new HashMap<>()).keySet()); + } + } else { + functions = new ArrayList<>(udfFunctions + .getOrDefault(functionRegistry.getGlobalFunctionDbName(), new HashMap<>()).keySet()); + } + return functions; + } + + private static String reAcquireDbName(ConnectContext ctx, String dbName) throws AnalysisException { + if (Strings.isNullOrEmpty(dbName)) { + dbName = ctx.getDatabase(); + if (Strings.isNullOrEmpty(dbName)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); + } + } + return dbName; + } + + public ShowResultSetMetaData getMetaData() { + if (isVerbose) { + return META_DATA; + } + return ShowResultSetMetaData.builder().addColumn(new Column("Function Name", ScalarType.STRING)).build(); + } + + /** + * handle show functions + */ + @VisibleForTesting + protected ShowResultSet handleShowFunctions(ConnectContext ctx, StmtExecutor executor) throws Exception { + if (!FunctionUtil.isGlobalFunction(type)) { + this.dbName = reAcquireDbName(ctx, dbName); + } + + if (!FunctionUtil.isGlobalFunction(type) && !Env.getCurrentEnv().getAccessManager() + .checkDbPriv(ConnectContext.get(), InternalCatalog.INTERNAL_CATALOG_NAME, dbName, PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR, + ConnectContext.get().getQualifiedUser(), dbName); + } + + List<List<String>> resultRowSet = getResultRowSet(ctx); + // Only success + ShowResultSetMetaData showMetaData = getMetaData(); + return new ShowResultSet(showMetaData, resultRowSet); + } + + @Override + public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception { + return handleShowFunctions(ctx, executor); + } + + @Override + public <R, C> R accept(PlanVisitor<R, C> visitor, C context) { + return visitor.visitShowFunctionsCommand(this, context); + } + +} 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 10a2d97eb73..a18f0aa418c 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 @@ -140,6 +140,7 @@ import org.apache.doris.nereids.trees.plans.commands.ShowDynamicPartitionCommand import org.apache.doris.nereids.trees.plans.commands.ShowEncryptKeysCommand; import org.apache.doris.nereids.trees.plans.commands.ShowEventsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowFrontendsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowFunctionsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowGrantsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowIndexStatsCommand; import org.apache.doris.nereids.trees.plans.commands.ShowLastInsertCommand; @@ -648,6 +649,10 @@ public interface CommandVisitor<R, C> { return visitCommand(showFrontendsCommand, context); } + default R visitShowFunctionsCommand(ShowFunctionsCommand showFunctionsCommand, C context) { + return visitCommand(showFunctionsCommand, context); + } + default R visitAdminRebalanceDiskCommand(AdminRebalanceDiskCommand adminRebalanceDiskCommand, C context) { return visitCommand(adminRebalanceDiskCommand, context); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java new file mode 100644 index 00000000000..2be61c7289b --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java @@ -0,0 +1,190 @@ +// 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.Analyzer; +import org.apache.doris.analysis.CreateUserStmt; +import org.apache.doris.analysis.GrantStmt; +import org.apache.doris.analysis.TablePattern; +import org.apache.doris.analysis.UserDesc; +import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.catalog.AccessPrivilege; +import org.apache.doris.catalog.AccessPrivilegeWithCols; +import org.apache.doris.catalog.Env; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.DdlException; +import org.apache.doris.common.UserException; +import org.apache.doris.mysql.privilege.Auth; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.utframe.TestWithFeService; + +import com.google.common.collect.Lists; +import mockit.Mocked; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class ShowFunctionsCommandTest extends TestWithFeService { + private Auth auth; + @Mocked + private Analyzer analyzer; + + @Override + protected void runBeforeAll() throws Exception { + createDatabase("test"); + createDatabase("test_no_priv"); + connectContext.setDatabase("test"); + createFunction( + "CREATE ALIAS FUNCTION test.test_for_create_function(bigint) WITH PARAMETER(id) AS CONCAT(LEFT(id,3),'****',RIGHT(id,4));"); + } + + @Test + void testGetFunctions() throws AnalysisException { + // test No database selected + ShowFunctionsCommand sf = new ShowFunctionsCommand("", true, false, null); + connectContext.setDatabase(""); + ShowFunctionsCommand finalSf = sf; + Assertions.assertThrows(AnalysisException.class, () -> finalSf.getFunctions(connectContext)); + + // test for builtin functions + connectContext.setDatabase("test"); // reset database + sf = new ShowFunctionsCommand("test", true, false, null); + List<String> re1 = sf.getFunctions(connectContext); + Assertions.assertTrue(re1.size() > 100); + + // test for not builtin functions + sf = new ShowFunctionsCommand("test", false, false, null); + List<String> re2 = sf.getFunctions(connectContext); + Assertions.assertEquals(1, re2.size()); + Assertions.assertEquals("test_for_create_function", re2.get(0)); + + // test for full builtin functions + sf = new ShowFunctionsCommand("test", true, true, null); + List<String> re3 = sf.getFunctions(connectContext); + Assertions.assertTrue(re3.size() > 100); + + // test for full not builtin functions + sf = new ShowFunctionsCommand("test", false, true, null); + List<String> re4 = sf.getFunctions(connectContext); + Assertions.assertEquals(1, re4.size()); + } + + @Test + void testGetResultRowSetByFunctions() throws AnalysisException { + // test for builtin functions + connectContext.setDatabase("test"); + ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, false, null); + List<String> func1 = sf.getFunctions(connectContext); + List<List<String>> re1 = sf.getResultRowSetByFunctions(func1); + Assertions.assertTrue(re1.size() > 100); + + // test for not builtin functions + sf = new ShowFunctionsCommand("test", false, false, null); + List<String> func2 = sf.getFunctions(connectContext); + List<List<String>> re2 = sf.getResultRowSetByFunctions(func2); + Assertions.assertEquals(1, re2.get(0).size()); + Assertions.assertEquals("test_for_create_function", re2.get(0).get(0)); + + // test for full builtin functions + sf = new ShowFunctionsCommand("test", true, true, null); + List<String> func3 = sf.getFunctions(connectContext); + List<List<String>> re3 = sf.getResultRowSetByFunctions(func3); + Assertions.assertTrue(re3.size() > 100); + Assertions.assertEquals("", re3.get(0).get(1)); + Assertions.assertEquals("", re3.get(0).get(2)); + Assertions.assertEquals("", re3.get(0).get(3)); + Assertions.assertEquals("", re3.get(0).get(4)); + + // test for full not builtin functions + sf = new ShowFunctionsCommand("test", false, true, null); + List<String> func4 = sf.getFunctions(connectContext); + List<List<String>> re4 = sf.getResultRowSetByFunctions(func4); + Assertions.assertEquals(5, re4.get(0).size()); + Assertions.assertEquals("test_for_create_function", re4.get(0).get(0)); + Assertions.assertEquals("", re4.get(0).get(1)); + Assertions.assertEquals("", re4.get(0).get(2)); + Assertions.assertEquals("", re4.get(0).get(3)); + Assertions.assertEquals("", re4.get(0).get(4)); + + // test for full not builtin functions with where condition + String where = "test_for_create_function%"; + sf = new ShowFunctionsCommand("test", false, true, where); + List<String> func5 = sf.getFunctions(connectContext); + List<List<String>> re5 = sf.getResultRowSetByFunctions(func5); + Assertions.assertEquals(5, re5.get(0).size()); + Assertions.assertEquals("test_for_create_function", re5.get(0).get(0)); + Assertions.assertEquals("", re5.get(0).get(1)); + Assertions.assertEquals("", re5.get(0).get(2)); + Assertions.assertEquals("", re5.get(0).get(3)); + Assertions.assertEquals("", re5.get(0).get(4)); + } + + @Test + void testLike() { + connectContext.setDatabase("test"); + ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, false, null); + Assertions.assertTrue(sf.like("test_for_create_function", "test_for_create_function%")); + } + + @Test + void testAuth() throws Exception { + auth = Env.getCurrentEnv().getAuth(); + TablePattern tablePattern1 = new TablePattern("test", "*"); + List<AccessPrivilegeWithCols> privileges1 = Lists + .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)); + UserIdentity user1 = new UserIdentity("cmy", "%"); + UserDesc userDesc = new UserDesc(user1, "12345", true); + CreateUserStmt createUserStmt = new CreateUserStmt(false, userDesc, null); + try { + createUserStmt.analyze(analyzer); + } catch (UserException e) { + e.printStackTrace(); + } + + try { + auth.createUser(createUserStmt); + } catch (DdlException e) { + e.printStackTrace(); + } + + GrantStmt grantStmt = new GrantStmt(user1, null, tablePattern1, privileges1); + try { + grantStmt.analyze(analyzer); + } catch (UserException e) { + e.printStackTrace(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + } + + ConnectContext ctx = ConnectContext.get(); + ctx.setCurrentUserIdentity(user1); + + // user1 have select privilege of db test + ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, true, null); + sf.handleShowFunctions(ctx, null); + + // but user1 have not select privilege of db test_no_priv + ShowFunctionsCommand sfNoPriv = new ShowFunctionsCommand("test_no_priv", true, true, null); + Assertions.assertThrows(AnalysisException.class, () -> sfNoPriv.handleShowFunctions(ctx, null)); + } +} diff --git a/regression-test/suites/nereids_p0/show/test_nereids_show_functions.groovy b/regression-test/suites/nereids_p0/show/test_nereids_show_functions.groovy new file mode 100644 index 00000000000..64dcb50de52 --- /dev/null +++ b/regression-test/suites/nereids_p0/show/test_nereids_show_functions.groovy @@ -0,0 +1,92 @@ +// 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. + +suite("test_nereids_show_functions") { + String dbName = "show_functions_db" + String functionName = "zzzyyyxxx" + + sql "CREATE DATABASE IF NOT EXISTS ${dbName}" + sql """CREATE ALIAS FUNCTION ${dbName}.${functionName}(INT) WITH PARAMETER(id) AS CONCAT(LEFT(id, 3), '****', RIGHT(id, 4));""" + + checkNereidsExecute("use ${dbName}; show builtin functions;") + checkNereidsExecute("use ${dbName}; show builtin functions like 'ye%'") + checkNereidsExecute("use ${dbName}; show full builtin functions;") + checkNereidsExecute("use ${dbName}; show full builtin functions like 'ye%';") + checkNereidsExecute("use ${dbName}; show functions;") + checkNereidsExecute("use ${dbName}; show functions like '${functionName}%'") + checkNereidsExecute("use ${dbName}; show full functions like '${functionName}%';") + def res = sql """use ${dbName}; show builtin functions like '%yow%';""" + assertTrue(res.size() == 1) + def res1 = sql """use ${dbName}; show functions;""" + assertTrue(res1.size() == 1) + def res2 = sql """use ${dbName}; show functions like '${functionName}%';""" + assertTrue(res2.size() == 1) + // in nereids, each column of 'show full functions' is empty string, except Signature. + def res3 = sql """use ${dbName}; show full functions like '${functionName}%';""" + assertTrue(res3.size() == 1) + assertEquals(res3.get(0).get(0), functionName) + assertEquals(res3.get(0).get(1), "") + assertEquals(res3.get(0).get(2), "") + assertEquals(res3.get(0).get(3), "") + assertEquals(res3.get(0).get(4), "") + + checkNereidsExecute("show builtin functions from ${dbName};") + checkNereidsExecute("show builtin functions from ${dbName} like 'ye%';") + checkNereidsExecute("show full builtin functions from ${dbName};") + checkNereidsExecute("show full builtin functions from ${dbName} like 'ye%';") + checkNereidsExecute("show functions from ${dbName};") + checkNereidsExecute("show functions from ${dbName} like '${functionName}%';") + checkNereidsExecute("show full functions from ${dbName};") + checkNereidsExecute("show full functions from ${dbName} like '${functionName}%';") + def res4 = sql """show builtin functions from ${dbName} like '%yow%';""" + assertTrue(res4.size() == 1) + def res5 = sql """show functions from ${dbName}""" + assertTrue(res5.size() == 1) + def res6 = sql """show functions from ${dbName} like '${functionName}%';""" + assertTrue(res6.size() == 1) + // in nereids, each column of 'show full functions' is empty string, except Signature. + def res7 = sql """show full functions from ${dbName} like '${functionName}%';""" + assertTrue(res7.size() == 1) + assertEquals(res7.get(0).get(0), functionName) + assertEquals(res7.get(0).get(1), "") + assertEquals(res7.get(0).get(2), "") + assertEquals(res7.get(0).get(3), "") + assertEquals(res7.get(0).get(4), "") + + checkNereidsExecute("show builtin functions in ${dbName};") + checkNereidsExecute("show builtin functions in ${dbName} like 'ye%';") + checkNereidsExecute("show full builtin functions in ${dbName};") + checkNereidsExecute("show full builtin functions in ${dbName} like 'ye%';") + checkNereidsExecute("show functions in ${dbName};") + checkNereidsExecute("show functions in ${dbName} like '${functionName}%';") + checkNereidsExecute("show full functions in ${dbName};") + checkNereidsExecute("show full functions in ${dbName} like '${functionName}%';") + def res8 = sql """show builtin functions in ${dbName} like '%yow%';""" + assertTrue(res8.size() == 1) + def res9 = sql """show functions in ${dbName}""" + assertTrue(res9.size() == 1) + def res10 = sql """show functions in ${dbName} like '${functionName}%';""" + assertTrue(res10.size() == 1) + // in nereids, each column of 'show full functions' is empty string, except Signature. + def res11 = sql """show full functions in ${dbName} like '${functionName}%';""" + assertTrue(res11.size() == 1) + assertEquals(res11.get(0).get(0), functionName) + assertEquals(res11.get(0).get(1), "") + assertEquals(res11.get(0).get(2), "") + assertEquals(res11.get(0).get(3), "") + assertEquals(res11.get(0).get(4), "") +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org