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]