This is an automated email from the ASF dual-hosted git repository.

yiguolei 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 a2e00727d6 [feature](auth)support Col auth (#22629)
a2e00727d6 is described below

commit a2e00727d68b329e5c6a5fda59d3e915f9649d05
Author: zhangdong <[email protected]>
AuthorDate: Tue Aug 15 15:32:51 2023 +0800

    [feature](auth)support Col auth (#22629)
    
    support GRANT privilege[(col1,col2...)] [, privilege] ON db.tbl TO 
user_identity [ROLE 'role'];
---
 fe/fe-core/src/main/cup/sql_parser.cup             |  14 +-
 .../java/org/apache/doris/analysis/GrantStmt.java  | 100 ++++++++---
 .../java/org/apache/doris/analysis/RevokeStmt.java |  55 ++++---
 .../org/apache/doris/catalog/AccessPrivilege.java  |  32 ++--
 .../doris/catalog/AccessPrivilegeWithCols.java     |  87 ++++++++++
 .../org/apache/doris/common/proc/AuthProcDir.java  |   3 +-
 .../org/apache/doris/mysql/privilege/Auth.java     |  71 ++++++--
 .../doris/mysql/privilege/ColPrivilegeKey.java     |  98 +++++++++++
 .../apache/doris/mysql/privilege/PrivBitSet.java   |   3 +-
 .../doris/mysql/privilege/PrivPredicate.java       |  16 +-
 .../org/apache/doris/mysql/privilege/Role.java     | 134 ++++++++++++++-
 .../apache/doris/mysql/privilege/RoleManager.java  |   6 +-
 .../java/org/apache/doris/persist/PrivInfo.java    |  12 +-
 .../org/apache/doris/analysis/GrantStmtTest.java   |   9 +-
 .../doris/cooldown/CooldownConfHandlerTest.java    |   3 +-
 .../org/apache/doris/mysql/privilege/AuthTest.java | 182 +++++++++++++++++----
 .../nereids/rules/analysis/CheckRowPolicyTest.java |   6 +-
 .../org/apache/doris/persist/PrivInfoTest.java     |   2 +-
 .../java/org/apache/doris/policy/PolicyTest.java   |   7 +-
 19 files changed, 713 insertions(+), 127 deletions(-)

diff --git a/fe/fe-core/src/main/cup/sql_parser.cup 
b/fe/fe-core/src/main/cup/sql_parser.cup
index d589ff3937..aff48db22a 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -35,6 +35,7 @@ import org.apache.doris.analysis.SetOperationStmt.Operation;
 import org.apache.doris.analysis.SetOperationStmt.SetOperand;
 import org.apache.doris.analysis.LoadType;
 import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.PrimitiveType;
@@ -851,10 +852,10 @@ nonterminal List<AllPartitionDesc> 
all_partition_desc_list;
 nonterminal PartitionKeyDesc fixed_multi_partition_key_desc;
 nonterminal MultiPartitionDesc multi_partition_desc;
 
-nonterminal List<AccessPrivilege> privilege_list;
+nonterminal List<AccessPrivilegeWithCols> privilege_list;
 nonterminal List<String> string_list;
 nonterminal List<Long> integer_list, cancel_rollup_job_id_list;
-nonterminal AccessPrivilege privilege_type;
+nonterminal AccessPrivilegeWithCols privilege_type;
 
 nonterminal DataDescription data_desc, mysql_data_desc;
 nonterminal List<DataDescription> data_desc_list;
@@ -7108,16 +7109,17 @@ column_slice ::=
   ;
 
 privilege_type ::=
-    ident:name
+    ident:name opt_col_list:cols
     {:
-        RESULT = AccessPrivilege.fromName(name);
-        if (RESULT == null) {
+        AccessPrivilege accessPrivilege = AccessPrivilege.fromName(name);
+        if (accessPrivilege == null) {
             throw new AnalysisException("Unknown privilege type " + name);
         }
+        RESULT = new AccessPrivilegeWithCols(accessPrivilege, cols);
     :}
     | KW_ALL:id
     {:
-        RESULT = AccessPrivilege.ALL;
+        RESULT = new AccessPrivilegeWithCols(AccessPrivilege.ALL);
     :}
     ;
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
index 7608218397..aa0c21f0f0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
@@ -17,7 +17,7 @@
 
 package org.apache.doris.analysis;
 
-import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
@@ -26,19 +26,26 @@ import org.apache.doris.common.ErrorReport;
 import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.common.UserException;
 import org.apache.doris.mysql.privilege.Auth.PrivLevel;
-import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.mysql.privilege.Privilege;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+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.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 // GRANT STMT
-// GRANT privilege [, privilege] ON db.tbl TO user_identity [ROLE 'role'];
+// GRANT privilege[(col1,col2...)] [, privilege] ON db.tbl TO user_identity 
[ROLE 'role'];
 // GRANT privilege [, privilege] ON RESOURCE 'resource' TO user_identity [ROLE 
'role'];
 // GRANT role [, role] TO user_identity
 public class GrantStmt extends DdlStmt {
@@ -48,21 +55,27 @@ public class GrantStmt extends DdlStmt {
     private TablePattern tblPattern;
     private ResourcePattern resourcePattern;
     private WorkloadGroupPattern workloadGroupPattern;
-    private List<Privilege> privileges;
+    private Set<Privilege> privileges = Sets.newHashSet();
+    //Privilege,ctl,db,table -> cols
+    private Map<ColPrivilegeKey, Set<String>> colPrivileges = 
Maps.newHashMap();
     // Indicates that these roles are granted to a user
     private List<String> roles;
+    //AccessPrivileges will be parsed into two parts,
+    // with the column permissions section placed in "colPrivileges" and the 
others in "privileges"
+    private List<AccessPrivilegeWithCols> accessPrivileges;
 
-    public GrantStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern, List<AccessPrivilege> privileges) {
+    public GrantStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern,
+            List<AccessPrivilegeWithCols> privileges) {
         this(userIdent, role, tblPattern, null, null, privileges);
     }
 
     public GrantStmt(UserIdentity userIdent, String role,
-            ResourcePattern resourcePattern, List<AccessPrivilege> privileges) 
{
+            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> 
privileges) {
         this(userIdent, role, null, resourcePattern, null, privileges);
     }
 
     public GrantStmt(UserIdentity userIdent, String role,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> 
privileges) {
+            WorkloadGroupPattern workloadGroupPattern, 
List<AccessPrivilegeWithCols> privileges) {
         this(userIdent, role, null, null, workloadGroupPattern, privileges);
     }
 
@@ -72,17 +85,13 @@ public class GrantStmt extends DdlStmt {
     }
 
     private GrantStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern, ResourcePattern resourcePattern,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> 
privileges) {
+            WorkloadGroupPattern workloadGroupPattern, 
List<AccessPrivilegeWithCols> accessPrivileges) {
         this.userIdent = userIdent;
         this.role = role;
         this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
         this.workloadGroupPattern = workloadGroupPattern;
-        PrivBitSet privs = PrivBitSet.of();
-        for (AccessPrivilege accessPrivilege : privileges) {
-            privs.or(accessPrivilege.toPaloPrivilege());
-        }
-        this.privileges = privs.toPrivilegeList();
+        this.accessPrivileges = accessPrivileges;
     }
 
     public UserIdentity getUserIdent() {
@@ -109,7 +118,7 @@ public class GrantStmt extends DdlStmt {
         return role;
     }
 
-    public List<Privilege> getPrivileges() {
+    public Set<Privilege> getPrivileges() {
         return privileges;
     }
 
@@ -117,6 +126,10 @@ public class GrantStmt extends DdlStmt {
         return roles;
     }
 
+    public Map<ColPrivilegeKey, Set<String>> getColPrivileges() {
+        return colPrivileges;
+    }
+
     @Override
     public void analyze(Analyzer analyzer) throws UserException {
         super.analyze(analyzer);
@@ -141,12 +154,21 @@ public class GrantStmt extends DdlStmt {
             }
         }
 
-        if (CollectionUtils.isEmpty(privileges) && 
CollectionUtils.isEmpty(roles)) {
+
+        if (!CollectionUtils.isEmpty(accessPrivileges)) {
+            checkAccessPrivileges(accessPrivileges);
+
+            for (AccessPrivilegeWithCols accessPrivilegeWithCols : 
accessPrivileges) {
+                
accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, 
colPrivileges, tblPattern);
+            }
+        }
+
+        if (CollectionUtils.isEmpty(privileges) && 
CollectionUtils.isEmpty(roles) && MapUtils.isEmpty(colPrivileges)) {
             throw new AnalysisException("No privileges or roles in grant 
statement.");
         }
 
         if (tblPattern != null) {
-            checkTablePrivileges(privileges, role, tblPattern);
+            checkTablePrivileges(privileges, role, tblPattern, colPrivileges);
         } else if (resourcePattern != null) {
             checkResourcePrivileges(privileges, role, resourcePattern);
         } else if (workloadGroupPattern != null) {
@@ -156,6 +178,16 @@ public class GrantStmt extends DdlStmt {
         }
     }
 
+    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()));
+            }
+        }
+    }
+
     /**
      * Rules:
      * 1. ADMIN_PRIV and NODE_PRIV can only be granted/revoked on GLOBAL level
@@ -173,7 +205,8 @@ public class GrantStmt extends DdlStmt {
      * @param tblPattern
      * @throws AnalysisException
      */
-    public static void checkTablePrivileges(List<Privilege> privileges, String 
role, TablePattern tblPattern)
+    public static void checkTablePrivileges(Collection<Privilege> privileges, 
String role, TablePattern tblPattern,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges)
             throws AnalysisException {
         // Rule 1
         if (tblPattern.getPrivLevel() != PrivLevel.GLOBAL && 
(privileges.contains(Privilege.ADMIN_PRIV)
@@ -223,9 +256,14 @@ public class GrantStmt extends DdlStmt {
         if (privileges.contains(Privilege.USAGE_PRIV)) {
             throw new AnalysisException("Can not grant/revoke USAGE_PRIV 
to/from database or table");
         }
+
+        // Rule 7
+        if (!MapUtils.isEmpty(colPrivileges) && 
"*".equals(tblPattern.getTbl())) {
+            throw new AnalysisException("Col auth must specify specific 
table");
+        }
     }
 
-    public static void checkResourcePrivileges(List<Privilege> privileges, 
String role,
+    public static void checkResourcePrivileges(Collection<Privilege> 
privileges, String role,
             ResourcePattern resourcePattern) throws AnalysisException {
         for (int i = 0; i < Privilege.notBelongToResourcePrivileges.length; 
i++) {
             if 
(privileges.contains(Privilege.notBelongToResourcePrivileges[i])) {
@@ -256,7 +294,7 @@ public class GrantStmt extends DdlStmt {
         }
     }
 
-    public static void checkWorkloadGroupPrivileges(List<Privilege> 
privileges, String role,
+    public static void checkWorkloadGroupPrivileges(Collection<Privilege> 
privileges, String role,
             WorkloadGroupPattern workloadGroupPattern) throws 
AnalysisException {
         for (int i = 0; i < 
Privilege.notBelongToWorkloadGroupPrivileges.length; i++) {
             if 
(privileges.contains(Privilege.notBelongToWorkloadGroupPrivileges[i])) {
@@ -283,16 +321,34 @@ public class GrantStmt extends DdlStmt {
         }
     }
 
+    public static String colPrivMapToString(Map<ColPrivilegeKey, Set<String>> 
colPrivileges) {
+        if (MapUtils.isEmpty(colPrivileges)) {
+            return "";
+        }
+        StringBuilder builder = new StringBuilder();
+        for (Entry<ColPrivilegeKey, Set<String>> entry : 
colPrivileges.entrySet()) {
+            builder.append(entry.getKey().getPrivilege());
+            builder.append("(");
+            builder.append(Joiner.on(", ").join(entry.getValue()));
+            builder.append(")");
+            builder.append(",");
+        }
+        return builder.deleteCharAt(builder.length() - 1).toString();
+    }
+
     @Override
     public String toSql() {
         StringBuilder sb = new StringBuilder();
         sb.append("GRANT ");
-        if (privileges != null) {
+        if (!CollectionUtils.isEmpty(privileges)) {
             sb.append(Joiner.on(", ").join(privileges));
-        } else {
+        }
+        if (!MapUtils.isEmpty(colPrivileges)) {
+            sb.append(colPrivMapToString(colPrivileges));
+        }
+        if (!CollectionUtils.isEmpty(roles)) {
             sb.append(Joiner.on(", ").join(roles));
         }
-
         if (tblPattern != null) {
             sb.append(" ON ").append(tblPattern).append(" TO ");
         } else if (resourcePattern != null) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
index e008e9a8a8..74e9a4de91 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
@@ -17,23 +17,27 @@
 
 package org.apache.doris.analysis;
 
-import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.FeNameFormat;
-import org.apache.doris.mysql.privilege.PrivBitSet;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
 import org.apache.doris.mysql.privilege.Privilege;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+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.List;
+import java.util.Map;
+import java.util.Set;
 
 // REVOKE STMT
 // revoke privilege from some user, this is an administrator operation.
-//
-// REVOKE privilege [, privilege] ON db.tbl FROM user_identity [ROLE 'role'];
+// REVOKE privilege[(col1,col2...)] [, privilege] ON db.tbl FROM user_identity 
[ROLE 'role'];
 // REVOKE privilege [, privilege] ON resource 'resource' FROM user_identity 
[ROLE 'role'];
 // REVOKE role [, role] FROM user_identity
 public class RevokeStmt extends DdlStmt {
@@ -43,21 +47,24 @@ public class RevokeStmt extends DdlStmt {
     private TablePattern tblPattern;
     private ResourcePattern resourcePattern;
     private WorkloadGroupPattern workloadGroupPattern;
-    private List<Privilege> privileges;
+    private Set<Privilege> privileges = Sets.newHashSet();
+    private Map<ColPrivilegeKey, Set<String>> colPrivileges = 
Maps.newHashMap();
     // Indicates that these roles are revoked from a user
     private List<String> roles;
+    List<AccessPrivilegeWithCols> accessPrivileges;
 
-    public RevokeStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern, List<AccessPrivilege> privileges) {
+    public RevokeStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern,
+            List<AccessPrivilegeWithCols> privileges) {
         this(userIdent, role, tblPattern, null, null, privileges);
     }
 
     public RevokeStmt(UserIdentity userIdent, String role,
-            ResourcePattern resourcePattern, List<AccessPrivilege> privileges) 
{
+            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> 
privileges) {
         this(userIdent, role, null, resourcePattern, null, privileges);
     }
 
     public RevokeStmt(UserIdentity userIdent, String role,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> 
privileges) {
+            WorkloadGroupPattern workloadGroupPattern, 
List<AccessPrivilegeWithCols> privileges) {
         this(userIdent, role, null, null, workloadGroupPattern, privileges);
     }
 
@@ -67,17 +74,13 @@ public class RevokeStmt extends DdlStmt {
     }
 
     private RevokeStmt(UserIdentity userIdent, String role, TablePattern 
tblPattern, ResourcePattern resourcePattern,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilege> 
privileges) {
+            WorkloadGroupPattern workloadGroupPattern, 
List<AccessPrivilegeWithCols> accessPrivileges) {
         this.userIdent = userIdent;
         this.role = role;
         this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
         this.workloadGroupPattern = workloadGroupPattern;
-        PrivBitSet privs = PrivBitSet.of();
-        for (AccessPrivilege accessPrivilege : privileges) {
-            privs.or(accessPrivilege.toPaloPrivilege());
-        }
-        this.privileges = privs.toPrivilegeList();
+        this.accessPrivileges = accessPrivileges;
     }
 
     public UserIdentity getUserIdent() {
@@ -100,7 +103,7 @@ public class RevokeStmt extends DdlStmt {
         return role;
     }
 
-    public List<Privilege> getPrivileges() {
+    public Set<Privilege> getPrivileges() {
         return privileges;
     }
 
@@ -108,6 +111,10 @@ public class RevokeStmt extends DdlStmt {
         return roles;
     }
 
+    public Map<ColPrivilegeKey, Set<String>> getColPrivileges() {
+        return colPrivileges;
+    }
+
     @Override
     public void analyze(Analyzer analyzer) throws AnalysisException {
         if (userIdent != null) {
@@ -130,14 +137,20 @@ public class RevokeStmt extends DdlStmt {
                 roles.set(i, 
ClusterNamespace.getFullName(analyzer.getClusterName(), originalRoleName));
             }
         }
+        if (!CollectionUtils.isEmpty(accessPrivileges)) {
+            GrantStmt.checkAccessPrivileges(accessPrivileges);
 
-        if (CollectionUtils.isEmpty(privileges) && 
CollectionUtils.isEmpty(roles)) {
+            for (AccessPrivilegeWithCols accessPrivilegeWithCols : 
accessPrivileges) {
+                
accessPrivilegeWithCols.transferAccessPrivilegeToDoris(privileges, 
colPrivileges, tblPattern);
+            }
+        }
+        if (CollectionUtils.isEmpty(privileges) && 
CollectionUtils.isEmpty(roles) && MapUtils.isEmpty(colPrivileges)) {
             throw new AnalysisException("No privileges or roles in revoke 
statement.");
         }
 
         // Revoke operation obey the same rule as Grant operation. reuse the 
same method
         if (tblPattern != null) {
-            GrantStmt.checkTablePrivileges(privileges, role, tblPattern);
+            GrantStmt.checkTablePrivileges(privileges, role, tblPattern, 
colPrivileges);
         } else if (resourcePattern != null) {
             GrantStmt.checkResourcePrivileges(privileges, role, 
resourcePattern);
         } else if (workloadGroupPattern != null) {
@@ -151,9 +164,13 @@ public class RevokeStmt extends DdlStmt {
     public String toSql() {
         StringBuilder sb = new StringBuilder();
         sb.append("REVOKE ");
-        if (privileges != null) {
+        if (!CollectionUtils.isEmpty(privileges)) {
             sb.append(Joiner.on(", ").join(privileges));
-        } else {
+        }
+        if (!MapUtils.isEmpty(colPrivileges)) {
+            sb.append(GrantStmt.colPrivMapToString(colPrivileges));
+        }
+        if (!CollectionUtils.isEmpty(roles)) {
             sb.append(Joiner.on(", ").join(roles));
         }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java
index 246805f08a..88c6c9649b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilege.java
@@ -17,10 +17,10 @@
 
 package org.apache.doris.catalog;
 
-import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.Privilege;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
 
 import java.util.List;
 
@@ -48,39 +48,43 @@ public enum AccessPrivilege {
         this.desc = desc;
     }
 
-    public PrivBitSet toPaloPrivilege() {
+    public List<Privilege> toDorisPrivilege() {
         Preconditions.checkState(flag > 0 && flag < 13);
         switch (flag) {
             case 1:
-                return PrivBitSet.of(Privilege.SELECT_PRIV);
+            case 6:
+                return Lists.newArrayList(Privilege.SELECT_PRIV);
             case 2:
             case 3:
-                return PrivBitSet.of(Privilege.SELECT_PRIV, 
Privilege.LOAD_PRIV,
+                return Lists.newArrayList(Privilege.SELECT_PRIV, 
Privilege.LOAD_PRIV,
                         Privilege.ALTER_PRIV, Privilege.CREATE_PRIV,
                         Privilege.DROP_PRIV);
             case 4:
-                return PrivBitSet.of(Privilege.NODE_PRIV);
+                return Lists.newArrayList(Privilege.NODE_PRIV);
             case 5:
-                return PrivBitSet.of(Privilege.GRANT_PRIV);
-            case 6:
-                return PrivBitSet.of(Privilege.SELECT_PRIV);
+                return Lists.newArrayList(Privilege.GRANT_PRIV);
             case 7:
-                return PrivBitSet.of(Privilege.LOAD_PRIV);
+                return Lists.newArrayList(Privilege.LOAD_PRIV);
             case 8:
-                return PrivBitSet.of(Privilege.ALTER_PRIV);
+                return Lists.newArrayList(Privilege.ALTER_PRIV);
             case 9:
-                return PrivBitSet.of(Privilege.CREATE_PRIV);
+                return Lists.newArrayList(Privilege.CREATE_PRIV);
             case 10:
-                return PrivBitSet.of(Privilege.DROP_PRIV);
+                return Lists.newArrayList(Privilege.DROP_PRIV);
             case 11:
-                return PrivBitSet.of(Privilege.ADMIN_PRIV);
+                return Lists.newArrayList(Privilege.ADMIN_PRIV);
             case 12:
-                return PrivBitSet.of(Privilege.USAGE_PRIV);
+                return Lists.newArrayList(Privilege.USAGE_PRIV);
             default:
                 return null;
         }
     }
 
+    // Used to restrict which permissions support column permissions
+    public boolean canHasColPriv() {
+        return this == SELECT_PRIV;
+    }
+
     public static AccessPrivilege fromName(String privStr) {
         try {
             return AccessPrivilege.valueOf(privStr.toUpperCase());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java
new file mode 100644
index 0000000000..9866949ecf
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/AccessPrivilegeWithCols.java
@@ -0,0 +1,87 @@
+// 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.catalog;
+
+import org.apache.doris.analysis.TablePattern;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
+import org.apache.doris.mysql.privilege.Privilege;
+
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class AccessPrivilegeWithCols {
+    private AccessPrivilege accessPrivilege;
+    private List<String> cols;
+
+    public AccessPrivilegeWithCols(AccessPrivilege accessPrivilege, 
List<String> cols) {
+        this.accessPrivilege = accessPrivilege;
+        this.cols = cols;
+    }
+
+    public AccessPrivilegeWithCols(AccessPrivilege accessPrivilege) {
+        this.accessPrivilege = accessPrivilege;
+    }
+
+    public AccessPrivilege getAccessPrivilege() {
+        return accessPrivilege;
+    }
+
+    public void setAccessPrivilege(AccessPrivilege accessPrivilege) {
+        this.accessPrivilege = accessPrivilege;
+    }
+
+    public List<String> getCols() {
+        return cols;
+    }
+
+    public void setCols(List<String> cols) {
+        this.cols = cols;
+    }
+
+    @Override
+    public String toString() {
+        return "AccessPrivilegeWithCols{"
+                + "accessPrivilege=" + accessPrivilege
+                + ", cols=" + cols
+                + '}';
+    }
+
+    public void transferAccessPrivilegeToDoris(Set<Privilege> privileges,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges, TablePattern 
tblPattern) {
+        List<Privilege> dorisPrivileges = accessPrivilege.toDorisPrivilege();
+        // if has no cols,represents the permissions assigned to the entire 
table
+        if (CollectionUtils.isEmpty(cols)) {
+            privileges.addAll(dorisPrivileges);
+        } else {
+            // if has cols, represents the permissions assigned to the cols
+            for (Privilege privilege : dorisPrivileges) {
+                ColPrivilegeKey colPrivilegeKey = new 
ColPrivilegeKey(privilege, tblPattern.getQualifiedCtl(),
+                        tblPattern.getQualifiedDb(), tblPattern.getTbl());
+                if (colPrivileges.containsKey(colPrivilegeKey)) {
+                    colPrivileges.get(colPrivilegeKey).addAll(cols);
+                } else {
+                    colPrivileges.put(colPrivilegeKey, Sets.newHashSet(cols));
+                }
+            }
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
index e4cc0c96ec..6adc86ee26 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
@@ -32,7 +32,8 @@ import com.google.common.collect.ImmutableList;
 public class AuthProcDir implements ProcDirInterface {
     public static final ImmutableList<String> TITLE_NAMES = new 
ImmutableList.Builder<String>()
             
.add("UserIdentity").add("Password").add("Roles").add("GlobalPrivs").add("CatalogPrivs")
-            
.add("DatabasePrivs").add("TablePrivs").add("ResourcePrivs").add("WorkloadGroupPrivs").build();
+            
.add("DatabasePrivs").add("TablePrivs").add("ColPrivs").add("ResourcePrivs").add("WorkloadGroupPrivs")
+            .build();
 
     private Auth auth;
 
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 274ccb56bf..6e249516f2 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
@@ -67,6 +67,7 @@ import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 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.logging.log4j.LogManager;
@@ -332,17 +333,29 @@ public class Auth implements Writable {
     }
 
     // ==== Column ====
+    // The reason why this method throws an exception instead of returning a 
boolean is to
+    // indicate which col does not have permission
     public void checkColsPriv(UserIdentity currentUser, String ctl, String db, 
String tbl, Set<String> cols,
             PrivPredicate wanted) throws AuthorizationException {
-        // TODO: Support column priv
-        // we check if have tbl priv,until internal support col auth.
-        if (!checkTblPriv(currentUser, ctl, db, tbl, wanted)) {
-            throw new AuthorizationException(String.format(
-                    "Permission denied: user [%s] does not have privilege for 
[%s] command on [%s].[%s].[%s]",
-                    currentUser, wanted, ctl, db, tbl));
+        Set<Role> roles = getRolesByUserWithLdap(currentUser);
+        for (String col : cols) {
+            if (!checkColPriv(ctl, db, tbl, col, wanted, roles)) {
+                throw new AuthorizationException(String.format(
+                        "Permission denied: user [%s] does not have privilege 
for [%s] command on [%s].[%s].[%s].[%s]",
+                        currentUser, wanted, ctl, db, tbl, col));
+            }
         }
     }
 
+    private boolean checkColPriv(String ctl, String db, String tbl,
+            String col, PrivPredicate wanted, Set<Role> roles) {
+        for (Role role : roles) {
+            if (role.checkColPriv(ctl, db, tbl, col, wanted)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
     // ==== Resource ====
     public boolean checkResourcePriv(UserIdentity currentUser, String 
resourceName, PrivPredicate wanted) {
@@ -520,7 +533,7 @@ public class Auth implements Writable {
         if (stmt.getTblPattern() != null) {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), 
stmt.getTblPattern(), privs,
-                    true /* err on non exist */, false /* not replay */);
+                    stmt.getColPrivileges(), true /* err on non exist */, 
false /* not replay */);
         } else if (stmt.getResourcePattern() != null) {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), 
stmt.getResourcePattern(), privs,
@@ -538,7 +551,7 @@ public class Auth implements Writable {
         try {
             if (privInfo.getTblPattern() != null) {
                 grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
-                        privInfo.getTblPattern(), privInfo.getPrivs(),
+                        privInfo.getTblPattern(), privInfo.getPrivs(), 
privInfo.getColPrivileges(),
                         true /* err on non exist */, true /* is replay */);
             } else if (privInfo.getResourcePattern() != null) {
                 grantInternal(privInfo.getUserIdent(), privInfo.getRole(),
@@ -558,8 +571,8 @@ public class Auth implements Writable {
 
     // grant for TablePattern
     //if no role,role is default role of userIdent
-    private void grantInternal(UserIdentity userIdent, String role, 
TablePattern tblPattern,
-            PrivBitSet privs, boolean errOnNonExist, boolean isReplay)
+    private void grantInternal(UserIdentity userIdent, String role, 
TablePattern tblPattern, PrivBitSet privs,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean 
errOnNonExist, boolean isReplay)
             throws DdlException {
         writeLock();
         try {
@@ -569,10 +582,10 @@ public class Auth implements Writable {
                 }
                 role = roleManager.getUserDefaultRoleName(userIdent);
             }
-            Role newRole = new Role(role, tblPattern, privs);
+            Role newRole = new Role(role, tblPattern, privs, colPrivileges);
             roleManager.addOrMergeRole(newRole, false /* err on exist */);
             if (!isReplay) {
-                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, 
null, role);
+                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, 
null, role, colPrivileges);
                 Env.getCurrentEnv().getEditLog().logGrantPriv(info);
             }
             LOG.info("finished to grant privilege. is replay: {}", isReplay);
@@ -672,7 +685,7 @@ public class Auth implements Writable {
         if (stmt.getTblPattern() != null) {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), 
stmt.getTblPattern(), privs,
-                    true /* err on non exist */, false /* is replay */);
+                    stmt.getColPrivileges(), true /* err on non exist */, 
false /* is replay */);
         } else if (stmt.getResourcePattern() != null) {
             PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges());
             revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), 
stmt.getResourcePattern(), privs,
@@ -690,7 +703,7 @@ public class Auth implements Writable {
         try {
             if (info.getTblPattern() != null) {
                 revokeInternal(info.getUserIdent(), info.getRole(), 
info.getTblPattern(), info.getPrivs(),
-                        true /* err on non exist */, true /* is replay */);
+                        info.getColPrivileges(), true /* err on non exist */, 
true /* is replay */);
             } else if (info.getResourcePattern() != null) {
                 revokeInternal(info.getUserIdent(), info.getRole(), 
info.getResourcePattern(), info.getPrivs(),
                         true /* err on non exist */, true /* is replay */);
@@ -706,17 +719,18 @@ public class Auth implements Writable {
     }
 
     private void revokeInternal(UserIdentity userIdent, String role, 
TablePattern tblPattern,
-            PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws 
DdlException {
+            PrivBitSet privs, Map<ColPrivilegeKey, Set<String>> colPrivileges, 
boolean errOnNonExist, boolean isReplay)
+            throws DdlException {
         writeLock();
         try {
             if (role == null) {
                 role = roleManager.getUserDefaultRoleName(userIdent);
             }
             // revoke privs from role
-            roleManager.revokePrivs(role, tblPattern, privs, errOnNonExist);
+            roleManager.revokePrivs(role, tblPattern, privs, colPrivileges, 
errOnNonExist);
 
             if (!isReplay) {
-                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, 
null, role);
+                PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, 
null, role, colPrivileges);
                 Env.getCurrentEnv().getEditLog().logRevokePriv(info);
             }
             LOG.info("finished to revoke privilege. is replay: {}", isReplay);
@@ -1155,6 +1169,19 @@ public class Auth implements Writable {
             userAuthInfo.add(Joiner.on("; ").join(tblPrivs));
         }
 
+        // col
+        List<String> colPrivs = Lists.newArrayList();
+        for (Entry<ColPrivilegeKey, Set<String>> entry : 
getUserColPrivMap(userIdent).entrySet()) {
+            colPrivs.add(String.format("%s.%s.%s: %s%s", 
entry.getKey().getCtl(), entry.getKey().getDb(),
+                    entry.getKey().getTbl(), entry.getKey().getPrivilege(), 
entry.getValue()));
+        }
+
+        if (colPrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(colPrivs));
+        }
+
         // resource
         List<String> resourcePrivs = Lists.newArrayList();
         for (PrivEntry entry : getUserResourcePrivTable(userIdent).entries) {
@@ -1222,6 +1249,16 @@ public class Auth implements Writable {
         return table;
     }
 
+    private Map<ColPrivilegeKey, Set<String>> getUserColPrivMap(UserIdentity 
userIdentity) {
+        Map<ColPrivilegeKey, Set<String>> colPrivMap = Maps.newHashMap();
+        Set<Role> roles = getRolesByUserWithLdap(userIdentity);
+        for (Role role : roles) {
+            Role.mergeColPrivMap(colPrivMap, role.getColPrivMap());
+        }
+        return colPrivMap;
+    }
+
+
     private ResourcePrivTable getUserResourcePrivTable(UserIdentity 
userIdentity) {
         ResourcePrivTable table = new ResourcePrivTable();
         Set<Role> roles = getRolesByUserWithLdap(userIdentity);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java
new file mode 100644
index 0000000000..9d4b791cb0
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/ColPrivilegeKey.java
@@ -0,0 +1,98 @@
+// 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.mysql.privilege;
+
+import org.apache.doris.cluster.ClusterNamespace;
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.SerializedName;
+
+public class ColPrivilegeKey {
+
+    @SerializedName(value = "privilegeIdx")
+    private int privilegeIdx;
+    @SerializedName(value = "ctl")
+    private String ctl;
+    @SerializedName(value = "db")
+    private String db;
+    @SerializedName(value = "tbl")
+    private String tbl;
+
+    public ColPrivilegeKey(Privilege privilege, String ctl, String db, String 
tbl) {
+        this.privilegeIdx = privilege.getIdx();
+        this.ctl = ClusterNamespace.getNameFromFullName(ctl);
+        this.db = ClusterNamespace.getNameFromFullName(db);
+        this.tbl = ClusterNamespace.getNameFromFullName(tbl);
+    }
+
+    public Privilege getPrivilege() {
+        return Privilege.getPriv(privilegeIdx);
+    }
+
+    public int getPrivilegeIdx() {
+        return privilegeIdx;
+    }
+
+    public void setPrivilegeIdx(int privilegeIdx) {
+        this.privilegeIdx = privilegeIdx;
+    }
+
+    public String getCtl() {
+        return ctl;
+    }
+
+    public void setCtl(String ctl) {
+        this.ctl = ctl;
+    }
+
+    public String getDb() {
+        return db;
+    }
+
+    public void setDb(String db) {
+        this.db = db;
+    }
+
+    public String getTbl() {
+        return tbl;
+    }
+
+    public void setTbl(String tbl) {
+        this.tbl = tbl;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ColPrivilegeKey that = (ColPrivilegeKey) o;
+        return privilegeIdx == that.privilegeIdx
+                && Objects.equal(ctl, that.ctl)
+                && Objects.equal(db, that.db)
+                && Objects.equal(tbl, that.tbl);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(privilegeIdx, ctl, db, tbl);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
index 940a331fa9..f06646526e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
@@ -31,6 +31,7 @@ import com.google.gson.annotations.SerializedName;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 
 // ....0000000000
@@ -135,7 +136,7 @@ public class PrivBitSet implements Writable {
         return bitSet;
     }
 
-    public static PrivBitSet of(List<Privilege> privs) {
+    public static PrivBitSet of(Collection<Privilege> privs) {
         PrivBitSet bitSet = new PrivBitSet();
         for (Privilege priv : privs) {
             bitSet.set(priv.getIdx());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
index 6e8916db2d..61f820f674 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
@@ -19,6 +19,8 @@ package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.CompoundPredicate.Operator;
 
+import java.util.Optional;
+
 public class PrivPredicate {
 
     // user can 'see' this meta
@@ -34,7 +36,7 @@ public class PrivPredicate {
             Privilege.USAGE_PRIV),
             Operator.OR);
     public static final PrivPredicate SHOW_WORKLOAD_GROUP = 
PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
-                    Privilege.USAGE_PRIV),
+            Privilege.USAGE_PRIV),
             Operator.OR);
     // create/drop/alter/show user
     public static final PrivPredicate GRANT = 
PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
@@ -110,6 +112,18 @@ public class PrivPredicate {
         return op;
     }
 
+    // Determine which column Privilege correspond to PrivPredicate
+    //The current logic is to include a SELECT_ PRIV returns SELECT_ PRIV, if 
load is included_ PRIV returns LOAD_ PRIV,
+    // the order cannot be reversed
+    public Optional<Privilege> getColPrivilege() {
+        if (privs.get(Privilege.SELECT_PRIV.getIdx())) {
+            return Optional.of(Privilege.SELECT_PRIV);
+        } else if (privs.get(Privilege.LOAD_PRIV.getIdx())) {
+            return Optional.of(Privilege.LOAD_PRIV);
+        }
+        return Optional.empty();
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
index a4cb457f18..025b86fa63 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
@@ -22,6 +22,7 @@ import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeMetaVersion;
@@ -37,6 +38,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gson.annotations.SerializedName;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -45,6 +47,8 @@ import java.io.DataOutput;
 import java.io.IOException;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 public class Role implements Writable, GsonPostProcessable {
@@ -80,6 +84,8 @@ public class Role implements Writable, GsonPostProcessable {
     private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = 
Maps.newConcurrentMap();
     @SerializedName(value = "workloadGroupPatternToPrivs")
     private Map<WorkloadGroupPattern, PrivBitSet> workloadGroupPatternToPrivs 
= Maps.newConcurrentMap();
+    @SerializedName(value = "colPrivMap")
+    private Map<ColPrivilegeKey, Set<String>> colPrivMap = Maps.newHashMap();
 
     // Will not be persisted, generated by tblPatternToPrivs and 
resourcePatternToPrivs
     private GlobalPrivTable globalPrivTable = new GlobalPrivTable();
@@ -89,6 +95,7 @@ public class Role implements Writable, GsonPostProcessable {
     private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
     private WorkloadGroupPrivTable workloadGroupPrivTable = new 
WorkloadGroupPrivTable();
 
+
     @Deprecated
     private Set<UserIdentity> users = Sets.newConcurrentHashSet();
 
@@ -111,6 +118,14 @@ public class Role implements Writable, GsonPostProcessable 
{
         grantPrivs(tablePattern, privs.copy());
     }
 
+    public Role(String roleName, TablePattern tablePattern, PrivBitSet privs,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges) throws 
DdlException {
+        this.roleName = roleName;
+        this.tblPatternToPrivs.put(tablePattern, privs);
+        grantPrivs(tablePattern, privs.copy());
+        grantCols(colPrivileges);
+    }
+
     public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet 
privs) throws DdlException {
         this.roleName = roleName;
         this.resourcePatternToPrivs.put(resourcePattern, privs);
@@ -186,6 +201,18 @@ public class Role implements Writable, GsonPostProcessable 
{
             }
             grantPrivs(entry.getKey(), entry.getValue().copy());
         }
+        mergeColPrivMap(colPrivMap, other.colPrivMap);
+    }
+
+    public static void mergeColPrivMap(Map<ColPrivilegeKey, Set<String>> 
toColPrivMap,
+            Map<ColPrivilegeKey, Set<String>> fromColPrivMap) {
+        for (Entry<ColPrivilegeKey, Set<String>> entry : 
fromColPrivMap.entrySet()) {
+            if (toColPrivMap.containsKey(entry.getKey())) {
+                toColPrivMap.get(entry.getKey()).addAll(entry.getValue());
+            } else {
+                toColPrivMap.put(entry.getKey(), entry.getValue());
+            }
+        }
     }
 
     public void merge(Role other) throws DdlException {
@@ -227,7 +254,8 @@ public class Role implements Writable, GsonPostProcessable {
      */
     private boolean checkAnyPrivWithinCatalog(String ctl) {
         return dbPrivTable.hasPrivsOfCatalog(ctl)
-                || tablePrivTable.hasPrivsOfCatalog(ctl);
+                || tablePrivTable.hasPrivsOfCatalog(ctl)
+                || checkAnyColPrivWithinCtl(ctl);
 
     }
 
@@ -263,8 +291,42 @@ public class Role implements Writable, GsonPostProcessable 
{
      * if so, the database should be visible to this user.
      */
     private boolean checkAnyPrivWithinDb(String ctl, String db) {
-        return tablePrivTable.hasPrivsOfDb(ctl, db);
+        return tablePrivTable.hasPrivsOfDb(ctl, db) || 
checkAnyColPrivWithinDb(ctl, db);
+
+    }
+
+    private boolean checkAnyColPrivWithinCtl(String ctl) {
+        ctl = ClusterNamespace.getNameFromFullName(ctl);
+        for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) {
+            if (colPrivilegeKey.getCtl().equals(ctl)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
+    private boolean checkAnyColPrivWithinDb(String ctl, String db) {
+        ctl = ClusterNamespace.getNameFromFullName(ctl);
+        db = ClusterNamespace.getNameFromFullName(db);
+        for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) {
+            if (colPrivilegeKey.getCtl().equals(ctl) && 
colPrivilegeKey.getDb().equals(db)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkAnyColPrivWithinTbl(String ctl, String db, String 
tbl) {
+        ctl = ClusterNamespace.getNameFromFullName(ctl);
+        db = ClusterNamespace.getNameFromFullName(db);
+        tbl = ClusterNamespace.getNameFromFullName(tbl);
+        for (ColPrivilegeKey colPrivilegeKey : colPrivMap.keySet()) {
+            if (colPrivilegeKey.getCtl().equals(ctl) && 
colPrivilegeKey.getDb().equals(db) && colPrivilegeKey
+                    .getTbl().equals(tbl)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private boolean checkDbInternal(String ctl, String db, PrivPredicate 
wanted,
@@ -284,10 +346,32 @@ public class Role implements Writable, 
GsonPostProcessable {
                 || checkTblInternal(ctl, db, tbl, wanted, savedPrivs)) {
             return true;
         }
+
+        // if user has any privs of col in this table, and the wanted priv is 
SHOW, return true
+        if (ctl != null && db != null && tbl != null && wanted == 
PrivPredicate.SHOW && checkAnyColPrivWithinTbl(ctl,
+                db, tbl)) {
+            return true;
+        }
+
         LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, 
savedPrivs);
         return false;
     }
 
+    public boolean checkColPriv(String ctl, String db, String tbl, String col, 
PrivPredicate wanted) {
+        Optional<Privilege> colPrivilege = wanted.getColPrivilege();
+        Preconditions.checkState(colPrivilege.isPresent(), "this privPredicate 
should not use checkColPriv:" + wanted);
+        return checkTblPriv(ctl, db, tbl, wanted) || onlyCheckColPriv(ctl, db, 
tbl, col, colPrivilege.get());
+    }
+
+    private boolean onlyCheckColPriv(String ctl, String db, String tbl, String 
col,
+            Privilege colPrivilege) {
+        ColPrivilegeKey colPrivilegeKey = new ColPrivilegeKey(colPrivilege, 
ctl, db, tbl);
+        if (colPrivMap.containsKey(colPrivilegeKey)) {
+            return colPrivMap.get(colPrivilegeKey).contains(col);
+        }
+        return false;
+    }
+
     private boolean checkTblInternal(String ctl, String db, String tbl, 
PrivPredicate wanted, PrivBitSet savedPrivs) {
         tablePrivTable.getPrivs(ctl, db, tbl, savedPrivs);
         return Privilege.satisfy(savedPrivs, wanted);
@@ -371,6 +455,10 @@ public class Role implements Writable, GsonPostProcessable 
{
         return tablePrivTable;
     }
 
+    public Map<ColPrivilegeKey, Set<String>> getColPrivMap() {
+        return colPrivMap;
+    }
+
     public ResourcePrivTable getResourcePrivTable() {
         return resourcePrivTable;
     }
@@ -396,7 +484,9 @@ public class Role implements Writable, GsonPostProcessable {
 
 
     private void grantPrivs(ResourcePattern resourcePattern, PrivBitSet privs) 
throws DdlException {
-
+        if (privs.isEmpty()) {
+            return;
+        }
         // grant privs to user
         switch (resourcePattern.getPrivLevel()) {
             case GLOBAL:
@@ -412,12 +502,31 @@ public class Role implements Writable, 
GsonPostProcessable {
     }
 
     private void grantPrivs(WorkloadGroupPattern workloadGroupPattern, 
PrivBitSet privs) throws DdlException {
+        if (privs.isEmpty()) {
+            return;
+        }
         if 
(workloadGroupPattern.getPrivLevel().equals(PrivLevel.WORKLOAD_GROUP)) {
             
grantWorkloadGroupPrivs(workloadGroupPattern.getworkloadGroupName(), privs);
         }
     }
 
+    private void grantCols(Map<ColPrivilegeKey, Set<String>> colPrivileges) {
+        if (Objects.isNull(colPrivileges)) {
+            return;
+        }
+        for (Entry<ColPrivilegeKey, Set<String>> entry : 
colPrivileges.entrySet()) {
+            if (colPrivMap.containsKey(entry.getKey())) {
+                colPrivMap.get(entry.getKey()).addAll(entry.getValue());
+            } else {
+                colPrivMap.put(entry.getKey(), 
Sets.newHashSet(entry.getValue()));
+            }
+        }
+    }
+
     private void grantPrivs(TablePattern tblPattern, PrivBitSet privs) throws 
DdlException {
+        if (privs.isEmpty()) {
+            return;
+        }
         // grant privs to user
         switch (tblPattern.getPrivLevel()) {
             case GLOBAL:
@@ -501,7 +610,9 @@ public class Role implements Writable, GsonPostProcessable {
         workloadGroupPrivTable.addEntry(entry, false, false);
     }
 
-    public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, boolean 
errOnNonExist) throws DdlException {
+    public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, 
Map<ColPrivilegeKey, Set<String>> colPrivileges,
+            boolean errOnNonExist)
+            throws DdlException {
         PrivBitSet existingPriv = tblPatternToPrivs.get(tblPattern);
         if (existingPriv == null) {
             if (errOnNonExist) {
@@ -511,6 +622,21 @@ public class Role implements Writable, GsonPostProcessable 
{
         }
         existingPriv.remove(privs);
         revokePrivs(tblPattern, privs);
+        revokeCols(colPrivileges);
+    }
+
+    private void revokeCols(Map<ColPrivilegeKey, Set<String>> colPrivileges) {
+        if (Objects.isNull(colPrivileges)) {
+            return;
+        }
+        for (Entry<ColPrivilegeKey, Set<String>> entry : 
colPrivileges.entrySet()) {
+            if (colPrivMap.containsKey(entry.getKey())) {
+                colPrivMap.get(entry.getKey()).removeAll(entry.getValue());
+                if (CollectionUtils.isEmpty(colPrivMap.get(entry.getKey()))) {
+                    colPrivMap.remove(entry.getKey());
+                }
+            }
+        }
     }
 
     public void revokePrivs(ResourcePattern resourcePattern, PrivBitSet privs, 
boolean errOnNonExist)
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
index 285534d297..7b37b6f40e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
@@ -51,6 +51,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -101,7 +102,8 @@ public class RoleManager implements Writable {
         roles.remove(qualifiedRole);
     }
 
-    public Role revokePrivs(String name, TablePattern tblPattern, PrivBitSet 
privs, boolean errOnNonExist)
+    public Role revokePrivs(String name, TablePattern tblPattern, PrivBitSet 
privs,
+            Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean 
errOnNonExist)
             throws DdlException {
         Role existingRole = roles.get(name);
         if (existingRole == null) {
@@ -110,7 +112,7 @@ public class RoleManager implements Writable {
             }
             return null;
         }
-        existingRole.revokePrivs(tblPattern, privs, errOnNonExist);
+        existingRole.revokePrivs(tblPattern, privs, colPrivileges, 
errOnNonExist);
         return existingRole;
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java 
b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
index 1b6e448cc8..377356d85c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
@@ -26,6 +26,7 @@ import org.apache.doris.catalog.Env;
 import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
+import org.apache.doris.mysql.privilege.ColPrivilegeKey;
 import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.persist.gson.GsonUtils;
 
@@ -35,6 +36,8 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class PrivInfo implements Writable {
     @SerializedName(value = "userIdent")
@@ -51,6 +54,8 @@ public class PrivInfo implements Writable {
     private byte[] passwd;
     @SerializedName(value = "role")
     private String role;
+    @SerializedName(value = "colPrivileges")
+    private Map<ColPrivilegeKey, Set<String>> colPrivileges;
     @SerializedName(value = "passwordOptions")
     private PasswordOptions passwordOptions;
     // Indicates that these roles are granted to a user
@@ -75,7 +80,7 @@ public class PrivInfo implements Writable {
 
     // For grant/revoke
     public PrivInfo(UserIdentity userIdent, TablePattern tablePattern, 
PrivBitSet privs,
-            byte[] passwd, String role) {
+            byte[] passwd, String role, Map<ColPrivilegeKey, Set<String>> 
colPrivileges) {
         this.userIdent = userIdent;
         this.tblPattern = tablePattern;
         this.resourcePattern = null;
@@ -83,6 +88,7 @@ public class PrivInfo implements Writable {
         this.privs = privs;
         this.passwd = passwd;
         this.role = role;
+        this.colPrivileges = colPrivileges;
     }
 
     // For grant/revoke resource priv
@@ -150,6 +156,10 @@ public class PrivInfo implements Writable {
         return roles;
     }
 
+    public Map<ColPrivilegeKey, Set<String>> getColPrivileges() {
+        return colPrivileges;
+    }
+
     public static PrivInfo read(DataInput in) throws IOException {
         if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_113) {
             PrivInfo info = new PrivInfo();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
index bead7dae56..37bf2c8bfb 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
@@ -18,6 +18,7 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.UserException;
@@ -83,13 +84,13 @@ public class GrantStmtTest {
     public void testNormal() throws AnalysisException, UserException {
         GrantStmt stmt;
 
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.ALL);
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALL));
         stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new 
TablePattern("testDb", "*"), privileges);
         stmt.analyze(analyzer);
         Assert.assertEquals("testCluster:testUser", 
stmt.getUserIdent().getQualifiedUser());
         Assert.assertEquals("testCluster:testDb", 
stmt.getTblPattern().getQualifiedDb());
 
-        privileges = Lists.newArrayList(AccessPrivilege.READ_ONLY, 
AccessPrivilege.ALL);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.READ_ONLY), new 
AccessPrivilegeWithCols(AccessPrivilege.ALL));
         stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new 
TablePattern("testDb", "*"), privileges);
         stmt.analyze(analyzer);
     }
@@ -97,7 +98,7 @@ public class GrantStmtTest {
     @Test
     public void testResourceNormal() throws UserException {
         String resourceName = "spark0";
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.USAGE_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV));
         GrantStmt stmt = new GrantStmt(new UserIdentity("testUser", "%"), 
null, new ResourcePattern(resourceName), privileges);
         stmt.analyze(analyzer);
         Assert.assertEquals(resourceName, 
stmt.getResourcePattern().getResourceName());
@@ -113,7 +114,7 @@ public class GrantStmtTest {
     public void testUserFail() throws AnalysisException, UserException {
         GrantStmt stmt;
 
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.ALL);
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALL));
         stmt = new GrantStmt(new UserIdentity("", "%"), null, new 
TablePattern("testDb", "*"), privileges);
         stmt.analyze(analyzer);
         Assert.fail("No exception throws.");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java
index ea3e239533..5b35321d91 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/cooldown/CooldownConfHandlerTest.java
@@ -23,6 +23,7 @@ import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.MaterializedIndex;
@@ -69,7 +70,7 @@ public class CooldownConfHandlerTest extends 
TestWithFeService {
         user.analyze(SystemInfoService.DEFAULT_CLUSTER);
         CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user));
         Env.getCurrentEnv().getAuth().createUser(createUserStmt);
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.ADMIN_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV));
         TablePattern tablePattern = new TablePattern("*", "*", "*");
         tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER);
         GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, 
privileges);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
index 417060de97..4978304177 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
@@ -30,6 +30,7 @@ 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.DomainResolver;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.AnalysisException;
@@ -44,6 +45,7 @@ import org.apache.doris.qe.QueryState;
 import org.apache.doris.system.SystemInfoService;
 
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import mockit.Expectations;
 import mockit.Mocked;
 import org.junit.Assert;
@@ -373,7 +375,9 @@ public class AuthTest {
 
         // 9. grant for cmy@'%'
         TablePattern tablePattern = new TablePattern("*", "*");
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.CREATE_PRIV, AccessPrivilege.DROP_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV),
+                        new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         GrantStmt grantStmt = new GrantStmt(new UserIdentity("cmy", "%"), 
null, tablePattern, privileges);
 
         try {
@@ -409,7 +413,8 @@ public class AuthTest {
 
         // 10. grant auth for non exist user
         tablePattern = new TablePattern("*", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV, 
AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("nouser", "%"), null, 
tablePattern, privileges);
 
         try {
@@ -430,7 +435,8 @@ public class AuthTest {
 
         // 11. grant auth for user with non exist host
         tablePattern = new TablePattern("*", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, 
AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "%"), null, 
tablePattern, privileges);
 
         try {
@@ -451,7 +457,8 @@ public class AuthTest {
 
         // 12. grant db auth to exist user
         tablePattern = new TablePattern("db1", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, 
AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, 
tablePattern, privileges);
 
         try {
@@ -482,7 +489,8 @@ public class AuthTest {
 
         // 13. grant tbl auth to exist user
         tablePattern = new TablePattern("db2", "tbl2");
-        privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, 
AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, 
tablePattern, privileges);
 
         try {
@@ -513,7 +521,7 @@ public class AuthTest {
 
         // 13.1 grant external ctl tbl auth to exist user
         tablePattern = new TablePattern("ext_ctl", "ext_db1", "ext_tbl1");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, 
tablePattern, privileges);
 
         try {
@@ -545,7 +553,8 @@ public class AuthTest {
 
         // 14. grant db auth to zhangsan@['palo.domain1']
         tablePattern = new TablePattern("db3", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, 
AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", 
true), null, tablePattern, privileges);
 
         try {
@@ -570,7 +579,7 @@ public class AuthTest {
                 PrivPredicate.ALTER));
         // 15. grant new auth to exist priv entry (exist ALTER/DROP, add 
SELECT)
         tablePattern = new TablePattern("db3", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", 
true), null, tablePattern, privileges);
 
         try {
@@ -622,7 +631,7 @@ public class AuthTest {
 
         // 16. revoke privs from non exist user
         tablePattern = new TablePattern("*", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         RevokeStmt revokeStmt = new RevokeStmt(new UserIdentity("nouser", 
"%"), null, tablePattern, privileges);
 
         try {
@@ -643,7 +652,7 @@ public class AuthTest {
 
         // 17. revoke privs from non exist host
         tablePattern = new TablePattern("*", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("cmy", "172.%"), null, 
tablePattern, privileges);
 
         try {
@@ -664,7 +673,7 @@ public class AuthTest {
 
         // 18. revoke privs from non exist db
         tablePattern = new TablePattern("nodb", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, 
tablePattern, privileges);
 
         try {
@@ -685,7 +694,7 @@ public class AuthTest {
 
         // 19. revoke privs from user @ ip
         tablePattern = new TablePattern("*", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, 
tablePattern, privileges);
 
         try {
@@ -713,7 +722,7 @@ public class AuthTest {
 
         // 19. revoke tbl privs from user @ ip
         tablePattern = new TablePattern("db2", "tbl2");
-        privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "192.%"), 
null, tablePattern, privileges);
 
         try {
@@ -749,7 +758,7 @@ public class AuthTest {
 
         // 20. revoke privs from non exist user @ domain
         tablePattern = new TablePattern("db2", "tbl2");
-        privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "nodomain", 
true), null, tablePattern, privileges);
 
         try {
@@ -770,7 +779,7 @@ public class AuthTest {
 
         // 21. revoke privs from non exist db from user @ domain
         tablePattern = new TablePattern("nodb", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ALTER_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", 
"palo.domain1", true), null, tablePattern, privileges);
 
         try {
@@ -791,7 +800,7 @@ public class AuthTest {
 
         // 22. revoke privs from exist user @ domain
         tablePattern = new TablePattern("db3", "*");
-        privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", 
"palo.domain1", true), null, tablePattern, privileges);
 
         try {
@@ -920,7 +929,8 @@ public class AuthTest {
         Assert.assertTrue(hasException);
 
         // 25. grant auth to non exist role, will create this new role
-        privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, 
AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(null, "role2", new TablePattern("*", "*"), 
privileges);
         try {
             grantStmt.analyze(analyzer);
@@ -937,7 +947,8 @@ public class AuthTest {
         }
 
         // 26. grant auth to role
-        privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, 
AccessPrivilege.SELECT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV),
+                new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(null, "role1", new TablePattern("*", "*"), 
privileges);
         try {
             grantStmt.analyze(analyzer);
@@ -1008,7 +1019,7 @@ public class AuthTest {
                 PrivPredicate.DROP));
 
         // 29. revoke auth on non exist db from role1
-        privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         revokeStmt = new RevokeStmt(null, "role1", new TablePattern("nodb", 
"*"), privileges);
         try {
             revokeStmt.analyze(analyzer);
@@ -1027,7 +1038,7 @@ public class AuthTest {
         Assert.assertTrue(hasException);
 
         // 30. revoke auth from role1
-        privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.DROP_PRIV));
         revokeStmt = new RevokeStmt(null, "role1", new TablePattern("*", "*"), 
privileges);
         try {
             revokeStmt.analyze(analyzer);
@@ -1337,7 +1348,7 @@ public class AuthTest {
         createUserStmt.analyze(analyzer);
         auth.createUser(createUserStmt);
 
-        privileges = Lists.newArrayList(AccessPrivilege.NODE_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.NODE_PRIV));
         // 40.1 grant to non-global level, which is not allowed
         grantStmt = new GrantStmt(opUser, null, new TablePattern("db1", "*"), 
privileges);
         try {
@@ -1425,7 +1436,7 @@ public class AuthTest {
             e.printStackTrace();
         }
         // Now, we grant grant_priv to opUser, and check if it can than grant 
node_priv to other user
-        privileges = Lists.newArrayList(AccessPrivilege.GRANT_PRIV);
+        privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.GRANT_PRIV));
         grantStmt = new GrantStmt(opUser, null, new TablePattern("*", "*"), 
privileges);
         try {
             new Expectations() {
@@ -1459,6 +1470,109 @@ public class AuthTest {
         }
     }
 
+    @Test
+    public void testColAuth() {
+        // create user
+        UserIdentity userIdentity = new UserIdentity("colUser", "%");
+        UserDesc userDesc = new UserDesc(userIdentity, "12345", true);
+        CreateUserStmt createUserStmt = new CreateUserStmt(false, userDesc, 
null);
+        try {
+            createUserStmt.analyze(analyzer);
+            auth.createUser(createUserStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        // test table is *
+        GrantStmt grantStmt = new GrantStmt(userIdentity, null, new 
TablePattern("db1", "*"), Lists.newArrayList(
+                new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV, 
Lists.newArrayList("a", "b"))));
+        try {
+            grantStmt.analyze(analyzer);
+            Assert.fail();
+        } catch (UserException e) {
+            e.printStackTrace();
+        }
+
+        // test CREATE_PRIV with col
+        grantStmt = new GrantStmt(userIdentity, null, new TablePattern("db1", 
"tbl1"), Lists.newArrayList(
+                new AccessPrivilegeWithCols(AccessPrivilege.CREATE_PRIV, 
Lists.newArrayList("a", "b"))));
+        try {
+            grantStmt.analyze(analyzer);
+            Assert.fail();
+        } catch (UserException e) {
+            e.printStackTrace();
+        }
+
+        // test Select_PRIV with col
+        grantStmt = new GrantStmt(userIdentity, null, new TablePattern("db1", 
"tbl1"), Lists.newArrayList(
+                new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV, 
Lists.newArrayList("a", "b"))));
+        try {
+            grantStmt.analyze(analyzer);
+            auth.grant(grantStmt);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        // check has select priv of column 'a'
+        try {
+            accessManager
+                    .checkColumnsPriv(userIdentity, 
SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1",
+                            Sets.newHashSet("a"), PrivPredicate.SELECT);
+        } catch (UserException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+        // check has select priv of column 'c'
+        try {
+            accessManager
+                    .checkColumnsPriv(userIdentity, 
SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1",
+                            Sets.newHashSet("c"), PrivPredicate.SELECT);
+            Assert.fail();
+        } catch (UserException e) {
+            e.printStackTrace();
+        }
+        // check has load priv of column 'a'
+        try {
+            accessManager
+                    .checkColumnsPriv(userIdentity, 
SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1",
+                            Sets.newHashSet("a"), PrivPredicate.LOAD);
+            Assert.fail();
+        } catch (UserException e) {
+            e.printStackTrace();
+        }
+        // check 'create_priv' use checkColumnsPriv
+        try {
+            accessManager
+                    .checkColumnsPriv(userIdentity, 
SystemInfoService.DEFAULT_CLUSTER + ":db1", "tbl1",
+                            Sets.newHashSet("a"), PrivPredicate.CREATE);
+            Assert.fail();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        // check show priv on ctl when has col priv
+        boolean hasPriv = accessManager.checkCtlPriv(userIdentity, 
Auth.DEFAULT_CATALOG, PrivPredicate.SHOW);
+        if (!hasPriv) {
+            Assert.fail();
+        }
+        // check show priv on db when has col priv
+        hasPriv = accessManager.checkDbPriv(userIdentity, 
Auth.DEFAULT_CATALOG, "db1", PrivPredicate.SHOW);
+        if (!hasPriv) {
+            Assert.fail();
+        }
+        // check show priv on tbl when has col priv
+        hasPriv = accessManager.checkTblPriv(userIdentity, 
Auth.DEFAULT_CATALOG, "db1", "tbl1", PrivPredicate.SHOW);
+        if (!hasPriv) {
+            Assert.fail();
+        }
+        // check select priv on tbl when has col priv
+        hasPriv = accessManager.checkTblPriv(userIdentity, 
Auth.DEFAULT_CATALOG, "db1", "tbl1", PrivPredicate.SELECT);
+        if (hasPriv) {
+            Assert.fail();
+        }
+    }
+
     @Test
     public void testGrantRole() {
         UserIdentity userIdentity = new UserIdentity("testUser", "%");
@@ -1475,7 +1589,7 @@ public class AuthTest {
         }
         // grant select_priv on db 'db1' to role 'role1'
         GrantStmt grantStmt = new GrantStmt(null, role, new 
TablePattern("db1", "*"),
-                Lists.newArrayList(AccessPrivilege.SELECT_PRIV));
+                Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV)));
         try {
             grantStmt.analyze(analyzer);
             auth.grant(grantStmt);
@@ -1544,7 +1658,8 @@ public class AuthTest {
         ResourcePattern resourcePattern = new ResourcePattern(resourceName);
         String anyResource = "*";
         ResourcePattern anyResourcePattern = new ResourcePattern(anyResource);
-        List<AccessPrivilege> usagePrivileges = 
Lists.newArrayList(AccessPrivilege.USAGE_PRIV);
+        List<AccessPrivilegeWithCols> usagePrivileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV));
         UserDesc userDesc = new UserDesc(userIdentity, "12345", true);
 
         // ------ grant|revoke resource to|from user ------
@@ -1583,8 +1698,9 @@ public class AuthTest {
         Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, 
PrivPredicate.USAGE));
         // 3.1 grant 'notBelongToResourcePrivileges' on resource 'spark0' to 
'testUser'@'%'
         for (int i = 0; i < Privilege.notBelongToResourcePrivileges.length; 
i++) {
-            List<AccessPrivilege> notAllowedPrivileges = Lists
-                    
.newArrayList(AccessPrivilege.fromName(Privilege.notBelongToResourcePrivileges[i].getName()));
+            List<AccessPrivilegeWithCols> notAllowedPrivileges = Lists
+                    .newArrayList(new AccessPrivilegeWithCols(
+                            
AccessPrivilege.fromName(Privilege.notBelongToResourcePrivileges[i].getName())));
             grantStmt = new GrantStmt(userIdentity, null, resourcePattern, 
notAllowedPrivileges);
             try {
                 grantStmt.analyze(analyzer);
@@ -1791,7 +1907,8 @@ public class AuthTest {
         }
 
         // 1. grant db table priv to resource
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(userIdentity, null, resourcePattern, 
privileges);
         hasException = false;
         try {
@@ -1824,7 +1941,8 @@ public class AuthTest {
         WorkloadGroupPattern workloadGroupPattern = new 
WorkloadGroupPattern(workloadGroupName);
         String anyWorkloadGroup = "%";
         WorkloadGroupPattern anyWorkloadGroupPattern = new 
WorkloadGroupPattern(anyWorkloadGroup);
-        List<AccessPrivilege> usagePrivileges = 
Lists.newArrayList(AccessPrivilege.USAGE_PRIV);
+        List<AccessPrivilegeWithCols> usagePrivileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV));
         UserDesc userDesc = new UserDesc(userIdentity, "12345", true);
 
         // ------ grant|revoke workload group to|from user ------
@@ -1863,8 +1981,9 @@ public class AuthTest {
         Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, 
PrivPredicate.USAGE));
         // 3.1 grant 'notBelongToWorkloadGroupPrivileges'
         for (int i = 0; i < 
Privilege.notBelongToWorkloadGroupPrivileges.length; i++) {
-            List<AccessPrivilege> notAllowedPrivileges = Lists.newArrayList(
-                    
AccessPrivilege.fromName(Privilege.notBelongToWorkloadGroupPrivileges[i].getName()));
+            List<AccessPrivilegeWithCols> notAllowedPrivileges = 
Lists.newArrayList(
+                    new AccessPrivilegeWithCols(
+                            
AccessPrivilege.fromName(Privilege.notBelongToWorkloadGroupPrivileges[i].getName())));
             grantStmt = new GrantStmt(userIdentity, null, 
workloadGroupPattern, notAllowedPrivileges);
             try {
                 grantStmt.analyze(analyzer);
@@ -2072,7 +2191,8 @@ public class AuthTest {
         }
 
         // 1. grant db table priv to workload group
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.SELECT_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
         grantStmt = new GrantStmt(userIdentity, null, workloadGroupPattern, 
privileges);
         hasException = false;
         try {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
index 47e672db99..1464de74d8 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckRowPolicyTest.java
@@ -17,12 +17,14 @@
 
 package org.apache.doris.nereids.rules.analysis;
 
+import org.apache.doris.analysis.Analyzer;
 import org.apache.doris.analysis.CreateUserStmt;
 import org.apache.doris.analysis.GrantStmt;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Database;
@@ -89,10 +91,12 @@ public class CheckRowPolicyTest extends TestWithFeService {
         user.analyze(SystemInfoService.DEFAULT_CLUSTER);
         CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user));
         Env.getCurrentEnv().getAuth().createUser(createUserStmt);
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.ADMIN_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV));
         TablePattern tablePattern = new TablePattern("*", "*", "*");
         tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER);
         GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, 
privileges);
+        Analyzer analyzer = new Analyzer(connectContext.getEnv(), 
connectContext);
+        grantStmt.analyze(analyzer);
         Env.getCurrentEnv().getAuth().grant(grantStmt);
     }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
index d45c1e2ad7..5e02244afa 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
@@ -74,7 +74,7 @@ public class PrivInfoTest {
     @Test
     public void testWithTablePattern() throws IOException {
         PrivInfo privInfo = new PrivInfo(UserIdentity.ROOT, TablePattern.ALL, 
PrivBitSet.of(Privilege.DROP_PRIV),
-                new byte[] {'a', 'b', 'c'}, "role");
+                new byte[] {'a', 'b', 'c'}, "role", null);
 
         // 1. Write objects to file
         File file = new File("./privInfo");
diff --git a/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java
index 2415e0fb3c..b678f33614 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.policy;
 
+import org.apache.doris.analysis.Analyzer;
 import org.apache.doris.analysis.CreateUserStmt;
 import org.apache.doris.analysis.Expr;
 import org.apache.doris.analysis.GrantStmt;
@@ -25,6 +26,7 @@ import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AccessPrivilege;
+import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
@@ -67,10 +69,13 @@ public class PolicyTest extends TestWithFeService {
         user.analyze(SystemInfoService.DEFAULT_CLUSTER);
         CreateUserStmt createUserStmt = new CreateUserStmt(new UserDesc(user));
         Env.getCurrentEnv().getAuth().createUser(createUserStmt);
-        List<AccessPrivilege> privileges = 
Lists.newArrayList(AccessPrivilege.ADMIN_PRIV);
+        List<AccessPrivilegeWithCols> privileges = Lists
+                .newArrayList(new 
AccessPrivilegeWithCols(AccessPrivilege.ADMIN_PRIV));
         TablePattern tablePattern = new TablePattern("*", "*", "*");
         tablePattern.analyze(SystemInfoService.DEFAULT_CLUSTER);
         GrantStmt grantStmt = new GrantStmt(user, null, tablePattern, 
privileges);
+        Analyzer analyzer = new Analyzer(connectContext.getEnv(), 
connectContext);
+        grantStmt.analyze(analyzer);
         Env.getCurrentEnv().getAuth().grant(grantStmt);
         useUser("test_policy");
     }


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

Reply via email to