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 749004e57f5 [Enhancement] (nereids)implement grantCommand in nereids 
(#50203)
749004e57f5 is described below

commit 749004e57f53db60bc5a7ae30795113c4163fd49
Author: csding <[email protected]>
AuthorDate: Mon May 19 18:04:37 2025 +0800

    [Enhancement] (nereids)implement grantCommand in nereids (#50203)
    
    Issue Number: close
    [#42818](https://github.com/apache/doris/issues/42818)
    [#42820](https://github.com/apache/doris/issues/42820)
    [#42823](https://github.com/apache/doris/issues/42823)
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  10 +-
 .../org/apache/doris/mysql/privilege/Auth.java     |  27 +++
 .../doris/nereids/parser/LogicalPlanBuilder.java   | 148 +++++++++++++
 .../apache/doris/nereids/trees/plans/PlanType.java |   5 +-
 .../commands/GrantResourcePrivilegeCommand.java    | 238 +++++++++++++++++++++
 .../trees/plans/commands/GrantRoleCommand.java     |  86 ++++++++
 .../plans/commands/GrantTablePrivilegeCommand.java | 216 +++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |  16 ++
 .../GrantResourcePrivilegeCommandTest.java         | 134 ++++++++++++
 .../trees/plans/commands/GrantRoleCommandTest.java |  74 +++++++
 .../commands/GrantTablePrivilegeCommandTest.java   | 107 +++++++++
 .../nereids_p0/ddl/grant/test_grant_nereids.groovy | 209 ++++++++++++++++++
 12 files changed, 1266 insertions(+), 4 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 2c58279d677..cb4889547e9 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
@@ -68,6 +68,7 @@ statementBase
     | supportedKillStatement            #supportedKillStatementAlias
     | supportedStatsStatement           #supportedStatsStatementAlias
     | supportedTransactionStatement     #supportedTransactionStatementAlias
+    | supportedGrantRevokeStatement     #supportedGrantRevokeStatementAlias
     | unsupportedStatement              #unsupported
     ;
 
@@ -626,14 +627,17 @@ supportedTransactionStatement
     | ROLLBACK WORK? (AND NO? CHAIN)? (NO? RELEASE)?                           
     #transactionRollback
     ;
 
-unsupportedGrantRevokeStatement
+supportedGrantRevokeStatement
     : GRANT privilegeList ON multipartIdentifierOrAsterisk
         TO (userIdentify | ROLE identifierOrText)                              
       #grantTablePrivilege
     | GRANT privilegeList ON
         (RESOURCE | CLUSTER | COMPUTE GROUP | STAGE | STORAGE VAULT | WORKLOAD 
GROUP)
         identifierOrTextOrAsterisk TO (userIdentify | ROLE identifierOrText)   
       #grantResourcePrivilege
-    | GRANT roles+=identifierOrText (COMMA roles+=identifierOrText)* TO 
userIdentify    #grantRole
-    | REVOKE privilegeList ON multipartIdentifierOrAsterisk
+    | GRANT roles+=identifierOrText (COMMA roles+=identifierOrText)* TO 
userIdentify  #grantRole
+    ;
+
+unsupportedGrantRevokeStatement
+    : REVOKE privilegeList ON multipartIdentifierOrAsterisk
         FROM (userIdentify | ROLE identifierOrText)                            
       #revokeTablePrivilege
     | REVOKE privilegeList ON
         (RESOURCE | CLUSTER | COMPUTE GROUP | STAGE | STORAGE VAULT | WORKLOAD 
GROUP)
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
index cb252bd983b..abf8eb1a2f7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
@@ -62,6 +62,9 @@ import org.apache.doris.mysql.MysqlPassword;
 import org.apache.doris.mysql.authenticate.AuthenticateType;
 import org.apache.doris.mysql.authenticate.ldap.LdapManager;
 import org.apache.doris.mysql.authenticate.ldap.LdapUserInfo;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantResourcePrivilegeCommand;
+import org.apache.doris.nereids.trees.plans.commands.GrantRoleCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantTablePrivilegeCommand;
 import org.apache.doris.nereids.trees.plans.commands.info.AlterUserInfo;
 import org.apache.doris.nereids.trees.plans.commands.info.CreateUserInfo;
 import org.apache.doris.persist.AlterUserOperationLog;
@@ -668,6 +671,30 @@ public class Auth implements Writable {
         }
     }
 
+    public void grantRoleCommand(GrantRoleCommand command) throws DdlException 
{
+        grantInternal(command.getUserIdentity(), command.getRoles(), false);
+    }
+
+    public void grantTablePrivilegeCommand(GrantTablePrivilegeCommand command) 
throws DdlException {
+        PrivBitSet privs = PrivBitSet.of(command.getPrivileges());
+        grantInternal(command.getUserIdentity().orElse(null), 
command.getRole().orElse(null), command.getTablePattern(),
+                    privs, command.getColPrivileges(), true /* err on non 
exist */, false /* not replay */);
+    }
+
+    public void grantResourcePrivilegeCommand(GrantResourcePrivilegeCommand 
command) throws DdlException {
+        if (command.getResourcePattern().isPresent()) {
+            PrivBitSet privs = PrivBitSet.of(command.getPrivileges());
+            grantInternal(command.getUserIdentity().orElse(null), 
command.getRole().orElse(null),
+                    command.getResourcePattern().orElse(null), privs, true /* 
err on non exist */,
+                    false /* not replay */);
+        } else if (command.getWorkloadGroupPattern().isPresent()) {
+            PrivBitSet privs = PrivBitSet.of(command.getPrivileges());
+            grantInternal(command.getUserIdentity().orElse(null), 
command.getRole().orElse(null),
+                    command.getWorkloadGroupPattern().orElse(null),
+                    privs, true /* err on non exist */, false /* not replay 
*/);
+        }
+    }
+
     public void replayGrant(PrivInfo privInfo) {
         try {
             PrivBitSet privs = privInfo.getPrivs();
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 f6456eea7fd..b65bfac2728 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
@@ -30,15 +30,21 @@ import org.apache.doris.analysis.EncryptKeyName;
 import org.apache.doris.analysis.FunctionName;
 import org.apache.doris.analysis.PassVar;
 import org.apache.doris.analysis.PasswordOptions;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.SetType;
 import org.apache.doris.analysis.StageAndPattern;
 import org.apache.doris.analysis.StorageBackend;
 import org.apache.doris.analysis.TableName;
+import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.TableScanParams;
 import org.apache.doris.analysis.TableSnapshot;
 import org.apache.doris.analysis.TableValuedFunctionRef;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
+import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.BuiltinAggregateFunctions;
 import org.apache.doris.catalog.BuiltinTableGeneratingFunctions;
@@ -631,6 +637,9 @@ import 
org.apache.doris.nereids.trees.plans.commands.ExplainCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel;
 import org.apache.doris.nereids.trees.plans.commands.ExplainDictionaryCommand;
 import org.apache.doris.nereids.trees.plans.commands.ExportCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantResourcePrivilegeCommand;
+import org.apache.doris.nereids.trees.plans.commands.GrantRoleCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantTablePrivilegeCommand;
 import org.apache.doris.nereids.trees.plans.commands.HelpCommand;
 import org.apache.doris.nereids.trees.plans.commands.KillAnalyzeJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.KillConnectionCommand;
@@ -2037,6 +2046,24 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         }
     }
 
+    @Override
+    public String 
visitIdentifierOrTextOrAsterisk(DorisParser.IdentifierOrTextOrAsteriskContext 
ctx) {
+        if (ctx.ASTERISK() != null) {
+            return stripQuotes(ctx.ASTERISK().getText());
+        } else if (ctx.STRING_LITERAL() != null) {
+            return stripQuotes(ctx.STRING_LITERAL().getText());
+        } else {
+            return stripQuotes(ctx.identifier().getText());
+        }
+    }
+
+    @Override
+    public List<String> 
visitMultipartIdentifierOrAsterisk(DorisParser.MultipartIdentifierOrAsteriskContext
 ctx) {
+        return ctx.parts.stream()
+            .map(RuleContext::getText)
+            .collect(ImmutableList.toImmutableList());
+    }
+
     @Override
     public UserIdentity visitUserIdentify(UserIdentifyContext ctx) {
         String user = visitIdentifierOrText(ctx.user);
@@ -7045,6 +7072,127 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new TransactionRollbackCommand();
     }
 
+    @Override
+    public LogicalPlan 
visitGrantTablePrivilege(DorisParser.GrantTablePrivilegeContext ctx) {
+        List<AccessPrivilegeWithCols> accessPrivilegeWithCols = 
visitPrivilegeList(ctx.privilegeList());
+
+        List<String> parts = 
visitMultipartIdentifierOrAsterisk(ctx.multipartIdentifierOrAsterisk());
+        int size = parts.size();
+
+        if (size < 1) {
+            throw new AnalysisException("grant table privilege statement 
missing parameters");
+        }
+
+        TablePattern tablePattern = null;
+        if (size == 1) {
+            String db = parts.get(size - 1);
+            tablePattern = new TablePattern(db, "");
+        }
+
+        if (size == 2) {
+            String db = parts.get(size - 2);
+            String tbl = parts.get(size - 1);
+            tablePattern = new TablePattern(db, tbl);
+        }
+
+        if (size == 3) {
+            String ctl = parts.get(size - 3);
+            String db = parts.get(size - 2);
+            String tbl = parts.get(size - 1);
+            tablePattern = new TablePattern(ctl, db, tbl);
+        }
+
+        Optional<UserIdentity> userIdentity = Optional.empty();
+        Optional<String> role = Optional.empty();
+
+        if (ctx.ROLE() != null) {
+            role = Optional.of(visitIdentifierOrText(ctx.identifierOrText()));
+        } else if (ctx.userIdentify() != null) {
+            userIdentity = Optional.of(visitUserIdentify(ctx.userIdentify()));
+        }
+
+        return new GrantTablePrivilegeCommand(
+            accessPrivilegeWithCols,
+            tablePattern,
+            userIdentity,
+            role);
+    }
+
+    @Override
+    public LogicalPlan 
visitGrantResourcePrivilege(DorisParser.GrantResourcePrivilegeContext ctx) {
+        List<AccessPrivilegeWithCols> accessPrivilegeWithCols = 
visitPrivilegeList(ctx.privilegeList());
+        String name = 
visitIdentifierOrTextOrAsterisk(ctx.identifierOrTextOrAsterisk());
+
+        Optional<ResourcePattern> resourcePattern = Optional.empty();
+        Optional<WorkloadGroupPattern> workloadGroupPattern = Optional.empty();
+
+        if (ctx.CLUSTER() != null || ctx.COMPUTE() != null) {
+            resourcePattern = Optional.of(new ResourcePattern(name, 
ResourceTypeEnum.CLUSTER));
+        } else if (ctx.STAGE() != null) {
+            resourcePattern = Optional.of(new ResourcePattern(name, 
ResourceTypeEnum.STAGE));
+        } else if (ctx.STORAGE() != null) {
+            resourcePattern = Optional.of(new ResourcePattern(name, 
ResourceTypeEnum.STORAGE_VAULT));
+        } else if (ctx.RESOURCE() != null) {
+            resourcePattern = Optional.of(new ResourcePattern(name, 
ResourceTypeEnum.GENERAL));
+        } else if (ctx.WORKLOAD() != null) {
+            workloadGroupPattern = Optional.of(new WorkloadGroupPattern(name));
+        }
+
+        Optional<UserIdentity> userIdentity = Optional.empty();
+        Optional<String> role = Optional.empty();
+
+        if (ctx.ROLE() != null) {
+            role = Optional.of(visitIdentifierOrText(ctx.identifierOrText()));
+        } else if (ctx.userIdentify() != null) {
+            userIdentity = Optional.of(visitUserIdentify(ctx.userIdentify()));
+        }
+
+        return new GrantResourcePrivilegeCommand(
+            accessPrivilegeWithCols,
+            resourcePattern,
+            workloadGroupPattern,
+            role,
+            userIdentity);
+    }
+
+    @Override
+    public LogicalPlan visitGrantRole(DorisParser.GrantRoleContext ctx) {
+        UserIdentity userIdentity = visitUserIdentify(ctx.userIdentify());
+        List<String> roles = ctx.roles.stream()
+                .map(this::visitIdentifierOrText)
+                .collect(ImmutableList.toImmutableList());
+
+        if (roles.size() == 0) {
+            throw new AnalysisException("grant role statement lack of role");
+        }
+
+        return new GrantRoleCommand(userIdentity, roles);
+    }
+
+    @Override
+    public AccessPrivilegeWithCols visitPrivilege(DorisParser.PrivilegeContext 
ctx) {
+        AccessPrivilegeWithCols accessPrivilegeWithCols;
+        if (ctx.ALL() != null) {
+            AccessPrivilege accessPrivilege = AccessPrivilege.ALL;
+            accessPrivilegeWithCols = new 
AccessPrivilegeWithCols(accessPrivilege, ImmutableList.of());
+        } else {
+            String privilegeName = stripQuotes(ctx.name.getText());
+            AccessPrivilege accessPrivilege = 
AccessPrivilege.fromName(privilegeName);
+            List<String> columns = ctx.identifierList() == null
+                    ? ImmutableList.of() : 
visitIdentifierList(ctx.identifierList());
+            accessPrivilegeWithCols = new 
AccessPrivilegeWithCols(accessPrivilege, columns);
+        }
+
+        return accessPrivilegeWithCols;
+    }
+
+    @Override
+    public List<AccessPrivilegeWithCols> 
visitPrivilegeList(DorisParser.PrivilegeListContext ctx) {
+        return ctx.privilege().stream()
+            .map(this::visitPrivilege)
+            .collect(ImmutableList.toImmutableList());
+    }
+
     public LogicalPlan visitDropAnalyzeJob(DorisParser.DropAnalyzeJobContext 
ctx) {
         long jobId = Long.parseLong(ctx.INTEGER_VALUE().getText());
         return new DropAnalyzeJobCommand(jobId);
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 cf44afa6e45..d469575c4c5 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
@@ -376,5 +376,8 @@ public enum PlanType {
     CREATE_DATA_SYNC_JOB_COMMAND,
     CREATE_STAGE_COMMAND,
     DROP_STAGE_COMMAND,
-    TRUNCATE_TABLE_COMMAND
+    TRUNCATE_TABLE_COMMAND,
+    GRANT_ROLE_COMMAND,
+    GRANT_RESOURCE_PRIVILEGE_COMMAND,
+    GRANT_TABLE_PRIVILEGE_COMMAND
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommand.java
new file mode 100644
index 00000000000..64aab85fe45
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommand.java
@@ -0,0 +1,238 @@
+// 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.CompoundPredicate;
+import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.analysis.WorkloadGroupPattern;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.catalog.Env;
+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.FeNameFormat;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.Auth;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
+import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.mysql.privilege.Privilege;
+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.StmtExecutor;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * GRANT privilege [, privilege] ON RESOURCE 'resource' TO user_identity [ROLE 
'role'];
+ */
+public class GrantResourcePrivilegeCommand extends Command implements 
ForwardWithSync {
+    private final Optional<UserIdentity> userIdentity;
+    private final Optional<ResourcePattern> resourcePattern;
+    private final Optional<WorkloadGroupPattern> workloadGroupPattern;
+    private final Optional<String> role;
+    // AccessPrivileges will be parsed into two parts,
+    // with the column permissions section placed in "colPrivileges" and the 
others in "privileges"
+    private final List<AccessPrivilegeWithCols> accessPrivileges;
+
+    private Set<Privilege> privileges = Sets.newHashSet();
+    // Privilege,ctl,db,table -> cols
+    private Map<ColPrivilegeKey, Set<String>> colPrivileges = 
Maps.newHashMap();
+
+    /**
+     * GrantResourcePrivilegeCommand
+     */
+    public GrantResourcePrivilegeCommand(List<AccessPrivilegeWithCols> 
accessPrivileges,
+            Optional<ResourcePattern> resourcePattern, 
Optional<WorkloadGroupPattern> workloadGroupPattern,
+            Optional<String> role, Optional<UserIdentity> userIdentity) {
+        super(PlanType.GRANT_RESOURCE_PRIVILEGE_COMMAND);
+        this.accessPrivileges = Objects.requireNonNull(accessPrivileges, 
"accessPrivileges is null");
+        this.resourcePattern = Objects.requireNonNull(resourcePattern, 
"resourcePattern is null");
+        this.workloadGroupPattern = 
Objects.requireNonNull(workloadGroupPattern, "workloadGroupPattern is null");
+        this.role = Objects.requireNonNull(role, "role is null");
+        this.userIdentity = Objects.requireNonNull(userIdentity, "userIdentity 
is null");
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        validate();
+        Env.getCurrentEnv().getAuth().grantResourcePrivilegeCommand(this);
+    }
+
+    /**
+     * validate
+     */
+    public void validate() throws AnalysisException {
+        if (Config.access_controller_type.equalsIgnoreCase("ranger-doris")) {
+            throw new AnalysisException("Grant is prohibited when Ranger is 
enabled.");
+        }
+
+        if (userIdentity.isPresent()) {
+            userIdentity.get().analyze();
+        } else {
+            FeNameFormat.checkRoleName(role.get(), false /* can not be admin 
*/, "Can not grant to role");
+        }
+
+        if (resourcePattern.isPresent()) {
+            resourcePattern.get().analyze();
+        } else if (workloadGroupPattern.isPresent()) {
+            workloadGroupPattern.get().analyze();
+        }
+
+        if (!CollectionUtils.isEmpty(accessPrivileges)) {
+            checkAccessPrivileges(accessPrivileges);
+
+            for (AccessPrivilegeWithCols accessPrivilegeWithCols : 
accessPrivileges) {
+                
accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, 
colPrivileges, null);
+            }
+        }
+
+        if (CollectionUtils.isEmpty(privileges) && !role.isPresent() && 
MapUtils.isEmpty(colPrivileges)) {
+            throw new AnalysisException("No privileges or roles in grant 
statement.");
+        }
+
+        if (resourcePattern.isPresent()) {
+            PrivBitSet.convertResourcePrivToCloudPriv(resourcePattern.get(), 
privileges);
+            checkResourcePrivileges(privileges, resourcePattern.get());
+        } else if (workloadGroupPattern.isPresent()) {
+            checkWorkloadGroupPrivileges(privileges, 
workloadGroupPattern.get());
+        }
+    }
+
+    /**
+     * checkAccessPrivileges
+     */
+    public static void checkAccessPrivileges(
+            List<AccessPrivilegeWithCols> accessPrivileges) throws 
AnalysisException {
+        for (AccessPrivilegeWithCols access : accessPrivileges) {
+            if ((!access.getAccessPrivilege().canHasColPriv()) && 
!CollectionUtils
+                    .isEmpty(access.getCols())) {
+                throw new AnalysisException(
+                    String.format("%s do not support col auth.", 
access.getAccessPrivilege().name()));
+            }
+        }
+    }
+
+    /**
+     * checkWorkloadGroupPrivileges
+     */
+    public static void checkWorkloadGroupPrivileges(Collection<Privilege> 
privileges,
+            WorkloadGroupPattern workloadGroupPattern) throws 
AnalysisException {
+        
Privilege.checkIncorrectPrivilege(Privilege.notBelongToWorkloadGroupPrivileges, 
privileges);
+
+        PrivPredicate predicate = getPrivPredicate(privileges);
+        AccessControllerManager accessManager = 
Env.getCurrentEnv().getAccessManager();
+        if (!accessManager.checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)
+                && !accessManager.checkWorkloadGroupPriv(ConnectContext.get(),
+                workloadGroupPattern.getworkloadGroupName(), predicate)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
+                    predicate.getPrivs().toPrivilegeList());
+        }
+    }
+
+    /**
+     * checkResourcePrivileges
+     */
+    public static void checkResourcePrivileges(Collection<Privilege> 
privileges,
+            ResourcePattern resourcePattern) throws AnalysisException {
+        
Privilege.checkIncorrectPrivilege(Privilege.notBelongToResourcePrivileges, 
privileges);
+
+        PrivPredicate predicate = getPrivPredicate(privileges);
+        AccessControllerManager accessManager = 
Env.getCurrentEnv().getAccessManager();
+        if (!accessManager.checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)
+                && !checkResourcePriv(ConnectContext.get(), resourcePattern, 
predicate)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
+                    predicate.getPrivs().toPrivilegeList());
+        }
+    }
+
+    private static PrivPredicate getPrivPredicate(Collection<Privilege> 
privileges) {
+        ArrayList<Privilege> privs = Lists.newArrayList(privileges);
+        privs.add(Privilege.GRANT_PRIV);
+        return PrivPredicate.of(PrivBitSet.of(privs), 
CompoundPredicate.Operator.AND);
+    }
+
+    private static boolean checkResourcePriv(ConnectContext ctx, 
ResourcePattern resourcePattern,
+            PrivPredicate privPredicate) {
+        if (resourcePattern.getPrivLevel() == Auth.PrivLevel.GLOBAL) {
+            return Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ctx, 
privPredicate);
+        }
+
+        switch (resourcePattern.getResourceType()) {
+            case GENERAL:
+            case STORAGE_VAULT:
+                return Env.getCurrentEnv().getAccessManager()
+                    .checkResourcePriv(ctx, resourcePattern.getResourceName(), 
privPredicate);
+            case CLUSTER:
+            case STAGE:
+                return Env.getCurrentEnv().getAccessManager()
+                    .checkCloudPriv(ctx, resourcePattern.getResourceName(), 
privPredicate,
+                        resourcePattern.getResourceType());
+            default:
+                return true;
+        }
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitGrantResourcePrivilegeCommand(this, context);
+    }
+
+    public Optional<UserIdentity> getUserIdentity() {
+        return userIdentity;
+    }
+
+    public Optional<ResourcePattern> getResourcePattern() {
+        return resourcePattern;
+    }
+
+    public Optional<WorkloadGroupPattern> getWorkloadGroupPattern() {
+        return workloadGroupPattern;
+    }
+
+    public Optional<String> getRole() {
+        return role;
+    }
+
+    public List<AccessPrivilegeWithCols> getAccessPrivileges() {
+        return accessPrivileges;
+    }
+
+    public Map<ColPrivilegeKey, Set<String>> getColPrivileges() {
+        return colPrivileges;
+    }
+
+    public Set<Privilege> getPrivileges() {
+        return privileges;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommand.java
new file mode 100644
index 00000000000..a685f574512
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommand.java
@@ -0,0 +1,86 @@
+// 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.StmtType;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+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.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * GRANT role [, role] TO user_identity
+ */
+public class GrantRoleCommand extends Command implements ForwardWithSync {
+    private final UserIdentity userIdentity;
+    private final List<String> roles;
+
+    public GrantRoleCommand(UserIdentity userIdentity, List<String> roles) {
+        super(PlanType.GRANT_ROLE_COMMAND);
+        Preconditions.checkArgument(roles.size() > 0, "roles doesn't exist");
+
+        this.userIdentity = Objects.requireNonNull(userIdentity, "userIdentity 
is null");
+        this.roles = Objects.requireNonNull(roles, "roles is null");
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        validate();
+        Env.getCurrentEnv().getAuth().grantRoleCommand(this);
+    }
+
+    public void validate() throws AnalysisException {
+        userIdentity.analyze();
+        checkRolePrivileges();
+    }
+
+    public static void checkRolePrivileges() throws AnalysisException {
+        if 
(!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.GRANT)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"GRANT/REVOKE");
+        }
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitGrantRoleCommand(this, context);
+    }
+
+    @Override
+    public StmtType stmtType() {
+        return StmtType.GRANT;
+    }
+
+    public UserIdentity getUserIdentity() {
+        return userIdentity;
+    }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java
new file mode 100644
index 00000000000..df4f79970c9
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommand.java
@@ -0,0 +1,216 @@
+// 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.CompoundPredicate;
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.analysis.UserIdentity;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.catalog.Env;
+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.FeNameFormat;
+import org.apache.doris.mysql.privilege.AccessControllerManager;
+import org.apache.doris.mysql.privilege.Auth;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
+import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.mysql.privilege.Privilege;
+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.StmtExecutor;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * GRANT privilege[(col1,col2...)] [, privilege] ON db.tbl TO user_identity 
[ROLE 'role'];
+ */
+public class GrantTablePrivilegeCommand extends Command implements 
ForwardWithSync {
+    private final Optional<UserIdentity> userIdentity;
+    private final TablePattern tablePattern;
+    private final Optional<String> role;
+    // AccessPrivileges will be parsed into two parts,
+    // with the column permissions section placed in "colPrivileges" and the 
others in "privileges"
+    private final List<AccessPrivilegeWithCols> accessPrivileges;
+
+    private Set<Privilege> privileges = Sets.newHashSet();
+    // Privilege,ctl,db,table -> cols
+    private Map<ColPrivilegeKey, Set<String>> colPrivileges = 
Maps.newHashMap();
+
+    public GrantTablePrivilegeCommand(List<AccessPrivilegeWithCols> 
accessPrivileges, TablePattern tablePattern,
+                            Optional<UserIdentity> userIdentity, 
Optional<String> role) {
+        super(PlanType.GRANT_TABLE_PRIVILEGE_COMMAND);
+        this.accessPrivileges = Objects.requireNonNull(accessPrivileges, 
"accessPrivileges is null");
+        this.tablePattern = Objects.requireNonNull(tablePattern, "tablePattern 
is null");
+        this.userIdentity = Objects.requireNonNull(userIdentity, "userIdentity 
is null");
+        this.role = Objects.requireNonNull(role, "role is null");
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        validate();
+        Env.getCurrentEnv().getAuth().grantTablePrivilegeCommand(this);
+    }
+
+    /**
+     * validate
+     */
+    public void validate() throws AnalysisException {
+        if (Config.access_controller_type.equalsIgnoreCase("ranger-doris")) {
+            throw new AnalysisException("Grant is prohibited when Ranger is 
enabled.");
+        }
+
+        tablePattern.analyze();
+
+        if (userIdentity.isPresent()) {
+            userIdentity.get().analyze();
+        } else {
+            FeNameFormat.checkRoleName(role.get(), false /* can not be admin 
*/, "Can not grant to role");
+        }
+
+        if (!CollectionUtils.isEmpty(accessPrivileges)) {
+            checkAccessPrivileges(accessPrivileges);
+
+            for (AccessPrivilegeWithCols accessPrivilegeWithCols : 
accessPrivileges) {
+                
accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, 
colPrivileges, tablePattern);
+            }
+        }
+
+        if (CollectionUtils.isEmpty(privileges) && !role.isPresent() && 
MapUtils.isEmpty(colPrivileges)) {
+            throw new AnalysisException("No privileges or roles in grant 
statement.");
+        }
+
+        checkTablePrivileges(privileges, tablePattern, colPrivileges);
+    }
+
+    /**
+     * checkAccessPrivileges
+     */
+    public static void checkAccessPrivileges(List<AccessPrivilegeWithCols> 
accessPrivileges) throws AnalysisException {
+        for (AccessPrivilegeWithCols access : accessPrivileges) {
+            if ((!access.getAccessPrivilege().canHasColPriv()) && 
!CollectionUtils
+                    .isEmpty(access.getCols())) {
+                throw new AnalysisException(
+                    String.format("%s do not support col auth.", 
access.getAccessPrivilege().name()));
+            }
+        }
+    }
+
+    /**
+     * checkTablePrivileges
+     */
+    public static void checkTablePrivileges(Collection<Privilege> privileges, 
TablePattern tblPattern,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges) throws 
AnalysisException {
+        // Rule 1
+        
Privilege.checkIncorrectPrivilege(Privilege.notBelongToTablePrivileges, 
privileges);
+        // Rule 2
+        if (tblPattern.getPrivLevel() != Auth.PrivLevel.GLOBAL && 
(privileges.contains(Privilege.ADMIN_PRIV)
+                || privileges.contains(Privilege.NODE_PRIV))) {
+            throw new AnalysisException("ADMIN_PRIV and NODE_PRIV can only be 
granted/revoke on/from *.*.*");
+        }
+
+        // Rule 3
+        if (privileges.contains(Privilege.NODE_PRIV) && 
!Env.getCurrentEnv().getAccessManager()
+                .checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.OPERATOR)) {
+            throw new AnalysisException("Only user with NODE_PRIV can 
grant/revoke NODE_PRIV to other user");
+        }
+
+        // Rule 4
+        PrivPredicate predicate = getPrivPredicate(privileges);
+        AccessControllerManager accessManager = 
Env.getCurrentEnv().getAccessManager();
+        if (!accessManager.checkGlobalPriv(ConnectContext.get(), 
PrivPredicate.ADMIN)
+                && !checkTablePriv(ConnectContext.get(), predicate, 
tblPattern)) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ALL_ACCESS_DENIED_ERROR,
+                    predicate.getPrivs().toPrivilegeList());
+        }
+
+        // Rule 5
+        if (!MapUtils.isEmpty(colPrivileges) && 
"*".equals(tblPattern.getTbl())) {
+            throw new AnalysisException("Col auth must specify specific 
table");
+        }
+    }
+
+    private static PrivPredicate getPrivPredicate(Collection<Privilege> 
privileges) {
+        ArrayList<Privilege> privs = Lists.newArrayList(privileges);
+        privs.add(Privilege.GRANT_PRIV);
+        return PrivPredicate.of(PrivBitSet.of(privs), 
CompoundPredicate.Operator.AND);
+    }
+
+    private static boolean checkTablePriv(ConnectContext ctx, PrivPredicate 
wanted,
+            TablePattern tblPattern) {
+        AccessControllerManager accessManager = 
Env.getCurrentEnv().getAccessManager();
+        switch (tblPattern.getPrivLevel()) {
+            case GLOBAL:
+                return accessManager.checkGlobalPriv(ctx, wanted);
+            case CATALOG:
+                return accessManager.checkCtlPriv(ConnectContext.get(),
+                    tblPattern.getQualifiedCtl(), wanted);
+            case DATABASE:
+                return accessManager.checkDbPriv(ConnectContext.get(),
+                    tblPattern.getQualifiedCtl(), tblPattern.getQualifiedDb(), 
wanted);
+            default:
+                return accessManager.checkTblPriv(ConnectContext.get(), 
tblPattern.getQualifiedCtl(),
+                    tblPattern.getQualifiedDb(), tblPattern.getTbl(), wanted);
+
+        }
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitGrantTablePrivilegeCommand(this, context);
+    }
+
+    public Optional<UserIdentity> getUserIdentity() {
+        return userIdentity;
+    }
+
+    public Optional<String> getRole() {
+        return role;
+    }
+
+    public List<AccessPrivilegeWithCols> getAccessPrivileges() {
+        return accessPrivileges;
+    }
+
+    public Set<Privilege> getPrivileges() {
+        return privileges;
+    }
+
+    public Map<ColPrivilegeKey, Set<String>> getColPrivileges() {
+        return colPrivileges;
+    }
+
+    public TablePattern getTablePattern() {
+        return tablePattern;
+    }
+}
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 32190e1a0f1..9171bab18e3 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
@@ -106,6 +106,9 @@ import 
org.apache.doris.nereids.trees.plans.commands.DropWorkloadPolicyCommand;
 import org.apache.doris.nereids.trees.plans.commands.ExplainCommand;
 import org.apache.doris.nereids.trees.plans.commands.ExplainDictionaryCommand;
 import org.apache.doris.nereids.trees.plans.commands.ExportCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantResourcePrivilegeCommand;
+import org.apache.doris.nereids.trees.plans.commands.GrantRoleCommand;
+import 
org.apache.doris.nereids.trees.plans.commands.GrantTablePrivilegeCommand;
 import org.apache.doris.nereids.trees.plans.commands.HelpCommand;
 import org.apache.doris.nereids.trees.plans.commands.KillAnalyzeJobCommand;
 import org.apache.doris.nereids.trees.plans.commands.KillConnectionCommand;
@@ -1115,4 +1118,17 @@ public interface CommandVisitor<R, C> {
     default R visitTruncateTableCommand(TruncateTableCommand 
truncateTableCommand, C context) {
         return visitCommand(truncateTableCommand, context);
     }
+
+    default R visitGrantRoleCommand(GrantRoleCommand grantRoleCommand, C 
context) {
+        return visitCommand(grantRoleCommand, context);
+    }
+
+    default R visitGrantTablePrivilegeCommand(GrantTablePrivilegeCommand 
grantTablePrivilegeCommand, C context) {
+        return visitCommand(grantTablePrivilegeCommand, context);
+    }
+
+    default R visitGrantResourcePrivilegeCommand(GrantResourcePrivilegeCommand 
grantResourcePrivilegeCommand,
+            C context) {
+        return visitCommand(grantResourcePrivilegeCommand, context);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommandTest.java
new file mode 100644
index 00000000000..e502b47525c
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantResourcePrivilegeCommandTest.java
@@ -0,0 +1,134 @@
+// 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.ResourcePattern;
+import org.apache.doris.analysis.ResourceTypeEnum;
+import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+public class GrantResourcePrivilegeCommandTest extends TestWithFeService {
+    @Test
+    public void testValidate() {
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALL));
+        ResourcePattern resourcePattern = new ResourcePattern("test", 
ResourceTypeEnum.CLUSTER);
+        GrantResourcePrivilegeCommand command = new 
GrantResourcePrivilegeCommand(
+                privileges,
+                Optional.of(resourcePattern),
+                Optional.empty(),
+                Optional.of("test"),
+                Optional.empty());
+        Assertions.assertDoesNotThrow(() -> command.validate());
+    }
+
+    @Test
+    public void testResource() {
+        String createUserSql = "CREATE USER 'jack'";
+        String createRoleSql = "CREATE ROLE role1";
+        String resourceSql = "GRANT USAGE_PRIV ON RESOURCE 'jdbc_resource' TO 
'jack'@'%';";
+        String createJdbcResourceSql = "CREATE EXTERNAL RESOURCE 
\"jdbc_resource\"\n"
+                + "PROPERTIES\n"
+                + "(\n"
+                + " \"type\" = \"jdbc\",\n"
+                + " \"user\" = \"jdbc_user\",\n"
+                + " \"password\" = \"jdbc_passwd\",\n"
+                + " \"jdbc_url\" = 
\"jdbc:mysql://127.0.0.1:3316/doris_test?useSSL=false\",\n"
+                + " \"driver_url\" = 
\"https://doris-community-test-1308700295.cos.ap-hongkong.myqcloud.com/jdbc_driver/mysql-connector-java-8.0.25.jar\",\n";
+                + " \"driver_class\" = \"com.mysql.cj.jdbc.Driver\"\n"
+                + ");";
+
+        NereidsParser nereidsParser = new NereidsParser();
+
+        LogicalPlan logicalPlan = nereidsParser.parseSingle(createUserSql);
+        Assertions.assertTrue(logicalPlan instanceof CreateUserCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateUserCommand) 
logicalPlan).run(connectContext, null));
+
+        LogicalPlan logicalPlan1 = nereidsParser.parseSingle(createRoleSql);
+        Assertions.assertTrue(logicalPlan1 instanceof CreateRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateRoleCommand) 
logicalPlan1).run(connectContext, null));
+
+        LogicalPlan logicalPlan2 = 
nereidsParser.parseSingle(createJdbcResourceSql);
+        Assertions.assertTrue(logicalPlan2 instanceof CreateResourceCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateResourceCommand) 
logicalPlan2).run(connectContext, null));
+
+        LogicalPlan plan = nereidsParser.parseSingle(resourceSql);
+        Assertions.assertTrue(plan instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan).run(connectContext, null));
+
+        resourceSql = "GRANT USAGE_PRIV ON RESOURCE 'jdbc_resource' TO ROLE 
'role1';";
+        LogicalPlan plan1 = nereidsParser.parseSingle(resourceSql);
+        Assertions.assertTrue(plan instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan1).run(connectContext, null));
+
+        resourceSql = "GRANT USAGE_PRIV ON RESOURCE * TO 'jack'@'%';";
+        LogicalPlan plan2 = nereidsParser.parseSingle(resourceSql);
+        Assertions.assertTrue(plan instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan2).run(connectContext, null));
+    }
+
+    @Test
+    public void testWorkload() {
+        String createWorkLoadSql = "create workload group if not exists g1 \n"
+                + "properties (  \n"
+                + "\"cpu_share\"=\"10\", \n"
+                + "\"memory_limit\"=\"30%\", \n"
+                + "\"enable_memory_overcommit\"=\"true\" \n"
+                + ");";
+        String createUserSql = "CREATE USER 'jack1'";
+        String createRoleSql = "CREATE ROLE role2";
+        String workGroupSql = "GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO ROLE 
'role2';";
+
+        NereidsParser nereidsParser = new NereidsParser();
+
+        LogicalPlan logicalPlan = nereidsParser.parseSingle(createUserSql);
+        Assertions.assertTrue(logicalPlan instanceof CreateUserCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateUserCommand) 
logicalPlan).run(connectContext, null));
+
+        LogicalPlan logicalPlan1 = nereidsParser.parseSingle(createRoleSql);
+        Assertions.assertTrue(logicalPlan1 instanceof CreateRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateRoleCommand) 
logicalPlan1).run(connectContext, null));
+
+        LogicalPlan logicalPlan2 = 
nereidsParser.parseSingle(createWorkLoadSql);
+        Assertions.assertTrue(logicalPlan2 instanceof 
CreateWorkloadGroupCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateWorkloadGroupCommand) 
logicalPlan2).run(connectContext, null));
+
+        LogicalPlan plan = nereidsParser.parseSingle(workGroupSql);
+        Assertions.assertTrue(plan instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan).run(connectContext, null));
+
+        workGroupSql = "GRANT USAGE_PRIV ON WORKLOAD GROUP 'g1' TO 
'jack1'@'%';";
+        LogicalPlan plan1 = nereidsParser.parseSingle(workGroupSql);
+        Assertions.assertTrue(plan1 instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan1).run(connectContext, null));
+
+        workGroupSql = "GRANT USAGE_PRIV ON WORKLOAD GROUP '%' TO 
'jack1'@'%';";
+        LogicalPlan plan2 = nereidsParser.parseSingle(workGroupSql);
+        Assertions.assertTrue(plan2 instanceof GrantResourcePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantResourcePrivilegeCommand) 
plan2).run(connectContext, null));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommandTest.java
new file mode 100644
index 00000000000..939da85e3c4
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantRoleCommandTest.java
@@ -0,0 +1,74 @@
+// 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.UserIdentity;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+public class GrantRoleCommandTest extends TestWithFeService {
+    @Test
+    public void testValidate() throws DdlException {
+        GrantRoleCommand grantRoleCommand = new GrantRoleCommand(new 
UserIdentity("test", "%"), Arrays.asList("test"));
+        Assertions.assertDoesNotThrow(() -> grantRoleCommand.validate());
+
+        String grantRoleSql = "GRANT 'role1','role2' TO 'jack'@'%'";
+        String createUserSql = "CREATE USER 'jack'";
+        String role1 = "CREATE ROLE role1";
+        String role2 = "CREATE ROLE role2";
+
+        NereidsParser nereidsParser = new NereidsParser();
+
+        LogicalPlan logicalPlan = nereidsParser.parseSingle(createUserSql);
+        Assertions.assertTrue(logicalPlan instanceof CreateUserCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateUserCommand) 
logicalPlan).run(connectContext, null));
+
+        LogicalPlan logicalPlan1 = nereidsParser.parseSingle(role1);
+        Assertions.assertTrue(logicalPlan1 instanceof CreateRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateRoleCommand) 
logicalPlan1).run(connectContext, null));
+
+        LogicalPlan logicalPlan2 = nereidsParser.parseSingle(role2);
+        Assertions.assertTrue(logicalPlan2 instanceof CreateRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateRoleCommand) 
logicalPlan2).run(connectContext, null));
+
+        LogicalPlan logicalPlan3 = nereidsParser.parseSingle(grantRoleSql);
+        Assertions.assertTrue(logicalPlan3 instanceof GrantRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantRoleCommand) 
logicalPlan3).validate());
+        Env.getCurrentEnv().getAuth().grantRoleCommand((GrantRoleCommand) 
logicalPlan3);
+    }
+
+    @Test
+    public void testUserFail() {
+        // test no role
+        Assertions.assertThrows(IllegalArgumentException.class, () -> new 
GrantRoleCommand(new UserIdentity("", "%"), ImmutableList.of()));
+
+        // test no user
+        GrantRoleCommand grantRoleCommand = new GrantRoleCommand(new 
UserIdentity("", "%"), Arrays.asList("test"));
+        Assertions.assertThrows(AnalysisException.class, () -> 
grantRoleCommand.validate());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java
new file mode 100644
index 00000000000..bd022f4e2af
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/GrantTablePrivilegeCommandTest.java
@@ -0,0 +1,107 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+public class GrantTablePrivilegeCommandTest extends TestWithFeService {
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test");
+        connectContext.setDatabase("test");
+
+        String createTableStr = "create table test.test_table(d1 date, k1 int, 
k2 bigint)"
+                + "duplicate key(d1, k1) "
+                + "PARTITION BY RANGE(d1)"
+                + "(PARTITION p20210901 VALUES [('2021-09-01'), 
('2021-09-02')))"
+                + "distributed by hash(k1) buckets 2 "
+                + "properties('replication_num' = '1');";
+        createTable(createTableStr);
+    }
+
+    @Test
+    public void testValidate() throws DdlException {
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALL));
+        TablePattern tablePattern = new TablePattern("test", "test_table");
+        GrantTablePrivilegeCommand command = new GrantTablePrivilegeCommand(
+                privileges, tablePattern, Optional.empty(), 
Optional.of("test"));
+        Assertions.assertDoesNotThrow(() -> command.validate());
+    }
+
+    @Test
+    public void testGrantTablePrivilege() {
+        String grantTablePrivilegeSql = "GRANT LOAD_PRIV ON internal.test.* TO 
ROLE 'role1';";
+        String createRoleSql = "CREATE ROLE role1";
+        String createUserSql = "CREATE USER 'jack'";
+
+        NereidsParser nereidsParser = new NereidsParser();
+
+        LogicalPlan logicalPlan = nereidsParser.parseSingle(createRoleSql);
+        Assertions.assertTrue(logicalPlan instanceof CreateRoleCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateRoleCommand) 
logicalPlan).run(connectContext, null));
+
+        LogicalPlan logicalPlan1 = nereidsParser.parseSingle(createUserSql);
+        Assertions.assertTrue(logicalPlan1 instanceof CreateUserCommand);
+        Assertions.assertDoesNotThrow(() -> ((CreateUserCommand) 
logicalPlan1).run(connectContext, null));
+
+        LogicalPlan plan = nereidsParser.parseSingle(grantTablePrivilegeSql);
+        Assertions.assertTrue(plan instanceof GrantTablePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantTablePrivilegeCommand) 
plan).run(connectContext, null));
+
+        grantTablePrivilegeSql = "GRANT SELECT_PRIV,ALTER_PRIV,LOAD_PRIV ON 
test.test_table  TO 'jack'";
+        LogicalPlan plan1 = nereidsParser.parseSingle(grantTablePrivilegeSql);
+        Assertions.assertTrue(plan1 instanceof GrantTablePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantTablePrivilegeCommand) 
plan1).run(connectContext, null));
+
+        grantTablePrivilegeSql = "GRANT SELECT_PRIV ON *.*.* TO 'jack'";
+        LogicalPlan plan2 = nereidsParser.parseSingle(grantTablePrivilegeSql);
+        Assertions.assertTrue(plan2 instanceof GrantTablePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantTablePrivilegeCommand) 
plan2).run(connectContext, null));
+
+        // grant database test all table
+        grantTablePrivilegeSql = "GRANT SELECT_PRIV ON test TO 'jack'";
+        LogicalPlan plan3 = nereidsParser.parseSingle(grantTablePrivilegeSql);
+        Assertions.assertTrue(plan3 instanceof GrantTablePrivilegeCommand);
+        Assertions.assertDoesNotThrow(() -> ((GrantTablePrivilegeCommand) 
plan3).run(connectContext, null));
+    }
+
+    @Test
+    public void testFail() {
+        String query = "GRANT LOAD_PRIV ON internal.test1.* TO ROLE 
'my_role';";
+
+        NereidsParser nereidsParser = new NereidsParser();
+
+        // test database not exist
+        LogicalPlan plan = nereidsParser.parseSingle(query);
+        Assertions.assertTrue(plan instanceof GrantTablePrivilegeCommand);
+        Assertions.assertThrows(DdlException.class, () -> 
((GrantTablePrivilegeCommand) plan).run(connectContext, null));
+    }
+}
diff --git 
a/regression-test/suites/nereids_p0/ddl/grant/test_grant_nereids.groovy 
b/regression-test/suites/nereids_p0/ddl/grant/test_grant_nereids.groovy
new file mode 100644
index 00000000000..bddee2d1a39
--- /dev/null
+++ b/regression-test/suites/nereids_p0/ddl/grant/test_grant_nereids.groovy
@@ -0,0 +1,209 @@
+// 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_grant_nereids)") {
+
+    def forComputeGroupStr = "";
+
+    //cloud-mode
+    if (isCloudMode()) {
+        def clusters = sql " SHOW CLUSTERS; "
+        assertTrue(!clusters.isEmpty())
+        def validCluster = clusters[0][0]
+        forComputeGroupStr = " for  $validCluster "
+    }
+
+    String user1 = 'test_grant_nereids_user1'
+    String user2 = 'test_grant_nereids_user2'
+    String role1 = 'test_grant_nereids_role1'
+    String role2 = 'test_grant_nereids_role2'
+    String pwd = 'C123_567p'
+
+    String dbName = 'test_grant_nereis_db'
+    String tableName1 = 'test_grant_nereids_table1'
+    String tableName2 = 'test_grant_nereids_table2'
+
+    String wg1 = 'test_workload_group_nereids_1'
+    String wg2 = 'test_workload_group_nereids_2'
+
+    String rg1 = 'test_resource_nereids_1_hdfs'
+    String rg2 = 'test_resource_nereids_2_hdfs'
+
+    try_sql("DROP USER ${user1}")
+    try_sql("DROP USER ${user2}")
+    try_sql("DROP role ${role1}")
+    try_sql("DROP role ${role2}")
+    sql """CREATE USER '${user1}' IDENTIFIED BY '${pwd}'"""
+    sql """CREATE USER '${user2}' IDENTIFIED BY '${pwd}'"""
+    checkNereidsExecute("grant select_priv on regression_test to ${user1};")
+    checkNereidsExecute("grant select_priv on regression_test to ${user2};")
+
+    sql """CREATE ROLE ${role1}"""
+    sql """CREATE ROLE ${role2}"""
+
+    //cloud-mode
+    if (isCloudMode()) {
+        def clusters = sql " SHOW CLUSTERS; "
+        assertTrue(!clusters.isEmpty())
+        def validCluster = clusters[0][0]
+        checkNereidsExecute("GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO 
${user1};")
+        checkNereidsExecute("GRANT USAGE_PRIV ON CLUSTER `${validCluster}` TO 
${user2};")
+    }
+
+    try_sql """drop table if exists ${dbName}.${tableName1}"""
+    sql """drop database if exists ${dbName}"""
+    sql """create database ${dbName}"""
+    sql """
+        CREATE TABLE IF NOT EXISTS ${dbName}.`${tableName1}` (
+            id BIGINT,
+            username VARCHAR(20)
+        )
+        DISTRIBUTED BY HASH(id) BUCKETS 2
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1"
+        );
+        """
+
+    try_sql """drop table if exists ${dbName}.${tableName2}"""
+    sql """
+        CREATE TABLE IF NOT EXISTS ${dbName}.`${tableName2}` (
+            id BIGINT,
+            username VARCHAR(20)
+        )
+        DISTRIBUTED BY HASH(id) BUCKETS 2
+        PROPERTIES (
+            "replication_num" = "1"
+        );
+        """
+
+    sql """drop WORKLOAD GROUP if exists '${wg1}' $forComputeGroupStr """
+    sql """drop WORKLOAD GROUP if exists '${wg2}' $forComputeGroupStr """
+    sql """CREATE WORKLOAD GROUP "${wg1}" $forComputeGroupStr
+        PROPERTIES (
+            "cpu_share"="10"
+        );"""
+    sql """CREATE WORKLOAD GROUP "${wg2}" $forComputeGroupStr
+        PROPERTIES (
+            "cpu_share"="10"
+        );"""
+
+    sql """DROP RESOURCE if exists ${rg1}"""
+    sql """DROP RESOURCE if exists ${rg2}"""
+    sql """
+            CREATE RESOURCE IF NOT EXISTS "${rg1}"
+            PROPERTIES(
+            "type"="hdfs",
+            "fs.defaultFS"="127.0.0.1:8120",
+            "hadoop.username"="hive",
+            "hadoop.password"="hive",
+            "dfs.nameservices" = "my_ha",
+            "dfs.ha.namenodes.my_ha" = "my_namenode1, my_namenode2",
+            "dfs.namenode.rpc-address.my_ha.my_namenode1" = "127.0.0.1:10000",
+            "dfs.namenode.rpc-address.my_ha.my_namenode2" = "127.0.0.1:10000",
+            "dfs.client.failover.proxy.provider" = 
"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
+            );
+        """
+    sql """
+            CREATE RESOURCE IF NOT EXISTS "${rg2}"
+            PROPERTIES(
+            "type"="hdfs",
+            "fs.defaultFS"="127.0.0.1:8120",
+            "hadoop.username"="hive",
+            "hadoop.password"="hive",
+            "dfs.nameservices" = "my_ha",
+            "dfs.ha.namenodes.my_ha" = "my_namenode1, my_namenode2",
+            "dfs.namenode.rpc-address.my_ha.my_namenode1" = "127.0.0.1:10000",
+            "dfs.namenode.rpc-address.my_ha.my_namenode2" = "127.0.0.1:10000",
+            "dfs.client.failover.proxy.provider" = 
"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
+            );
+        """
+
+    sql """ADMIN SET FRONTEND CONFIG ('experimental_enable_workload_group' = 
'true');"""
+    sql """set experimental_enable_pipeline_engine = true;"""
+
+    // user
+    checkNereidsExecute("grant select_priv on ${dbName}.${tableName1} to 
${user1};")
+    checkNereidsExecute("grant select_priv on ${dbName}.${tableName2} to 
${user1};")
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        sql "select username from ${dbName}.${tableName1}"
+    }
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        sql "select username from ${dbName}.${tableName2}"
+    }
+
+    sql """revoke select_priv on ${dbName}.${tableName1} from ${user1}"""
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        try {
+            sql "select username from ${dbName}.${tableName1}"
+        } catch (Exception e) {
+            log.info(e.getMessage())
+            assertTrue(e.getMessage().contains("denied"))
+        }
+    }
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        sql "select username from ${dbName}.${tableName2}"
+    }
+
+    // role
+    checkNereidsExecute("grant select_priv on ${dbName}.${tableName1} to ROLE 
'${role1}';")
+    checkNereidsExecute("grant Load_priv on ${dbName}.${tableName1} to ROLE 
'${role2}';")
+    checkNereidsExecute("grant '${role1}', '${role2}' to '${user2}';")
+    connect(user2, "${pwd}", context.config.jdbcUrl) {
+        sql "select username from ${dbName}.${tableName1}"
+        sql """insert into ${dbName}.`${tableName1}` values (4, "444")"""
+    }
+
+    sql """revoke '${role1}' from '${user2}'"""
+    connect(user2, "${pwd}", context.config.jdbcUrl) {
+        try {
+            sql "select username from ${dbName}.${tableName1}"
+        } catch (Exception e) {
+            log.info(e.getMessage())
+            assertTrue(e.getMessage().contains("denied"))
+        }
+    }
+    connect(user2, "${pwd}", context.config.jdbcUrl) {
+        sql """insert into ${dbName}.`${tableName1}` values (5, "555")"""
+    }
+
+    // workload group
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        sql """set workload_group = '${wg1}';"""
+        try {
+            sql "select username from ${dbName}.${tableName2}"
+        } catch (Exception e) {
+            log.info(e.getMessage())
+            assertTrue(e.getMessage().contains("denied"))
+        }
+    }
+    checkNereidsExecute("GRANT USAGE_PRIV ON WORKLOAD GROUP '${wg1}' TO 
'${user1}';")
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        sql """set workload_group = '${wg1}';"""
+        sql """select username from ${dbName}.${tableName2}"""
+    }
+
+    // resource group
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        def res = sql """SHOW RESOURCES;"""
+        assertTrue(res == [])
+    }
+    checkNereidsExecute("GRANT USAGE_PRIV ON RESOURCE ${rg1} TO ${user1};")
+    connect(user1, "${pwd}", context.config.jdbcUrl) {
+        def res = sql """SHOW RESOURCES;"""
+        assertTrue(res.size() == 10)
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to