You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by pr...@apache.org on 2014/11/18 09:47:54 UTC

[3/5] incubator-sentry git commit: SENTRY-74: Add column-level privileges for Hive/Impala (Dapeng Sun and Xiaomeng Huang via Prasad Mujumdar)

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/entity/JsonLogEntityFactory.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/entity/JsonLogEntityFactory.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/entity/JsonLogEntityFactory.java
index 2cc8194..90308f4 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/entity/JsonLogEntityFactory.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/entity/JsonLogEntityFactory.java
@@ -18,6 +18,8 @@
 
 package org.apache.sentry.provider.db.log.entity;
 
+import java.util.Set;
+
 import org.apache.hadoop.conf.Configuration;
 import org.apache.sentry.provider.db.log.util.CommandUtil;
 import org.apache.sentry.provider.db.log.util.Constants;
@@ -38,6 +40,8 @@ import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
 import org.apache.sentry.service.thrift.Status;
 import org.apache.sentry.service.thrift.TSentryResponseStatus;
 
+import com.google.common.collect.ImmutableSet;
+
 public class JsonLogEntityFactory {
 
   private static JsonLogEntityFactory factory = new JsonLogEntityFactory();
@@ -69,27 +73,50 @@ public class JsonLogEntityFactory {
     return amle;
   }
 
-  public JsonLogEntity createJsonLogEntity(
+  public Set<JsonLogEntity> createJsonLogEntitys(
       TAlterSentryRoleGrantPrivilegeRequest request,
       TAlterSentryRoleGrantPrivilegeResponse response, Configuration conf) {
+    ImmutableSet.Builder<JsonLogEntity> setBuilder = ImmutableSet.builder();
+    if (request.isSetPrivileges()) {
+      for (TSentryPrivilege privilege : request.getPrivileges()) {
+        JsonLogEntity logEntity = createJsonLogEntity(request, privilege, response, conf);
+        setBuilder.add(logEntity);
+      }
+    }
+    return setBuilder.build();
+  }
+
+  private JsonLogEntity createJsonLogEntity(
+      TAlterSentryRoleGrantPrivilegeRequest request, TSentryPrivilege privilege,
+      TAlterSentryRoleGrantPrivilegeResponse response, Configuration conf) {
     AuditMetadataLogEntity amle = createCommonAMLE(conf, response.getStatus(),
         request.getRequestorUserName(), request.getClass().getName());
     amle.setOperationText(CommandUtil.createCmdForGrantPrivilege(request));
-    TSentryPrivilege privilege = request.getPrivilege();
     amle.setDatabaseName(privilege.getDbName());
     amle.setTableName(privilege.getTableName());
     amle.setResourcePath(privilege.getURI());
-
     return amle;
   }
 
-  public JsonLogEntity createJsonLogEntity(
+  public Set<JsonLogEntity> createJsonLogEntitys(
       TAlterSentryRoleRevokePrivilegeRequest request,
       TAlterSentryRoleRevokePrivilegeResponse response, Configuration conf) {
+    ImmutableSet.Builder<JsonLogEntity> setBuilder = ImmutableSet.builder();
+    if (request.isSetPrivileges()) {
+      for (TSentryPrivilege privilege : request.getPrivileges()) {
+        JsonLogEntity logEntity = createJsonLogEntity(request, privilege, response, conf);
+        setBuilder.add(logEntity);
+      }
+    }
+    return setBuilder.build();
+  }
+
+  private JsonLogEntity createJsonLogEntity(
+      TAlterSentryRoleRevokePrivilegeRequest request, TSentryPrivilege privilege,
+      TAlterSentryRoleRevokePrivilegeResponse response, Configuration conf) {
     AuditMetadataLogEntity amle = createCommonAMLE(conf, response.getStatus(),
         request.getRequestorUserName(), request.getClass().getName());
     amle.setOperationText(CommandUtil.createCmdForRevokePrivilege(request));
-    TSentryPrivilege privilege = request.getPrivilege();
     amle.setDatabaseName(privilege.getDbName());
     amle.setTableName(privilege.getTableName());
     amle.setResourcePath(privilege.getURI());

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/CommandUtil.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/CommandUtil.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/CommandUtil.java
index 841eeb3..9beef83 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/CommandUtil.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/CommandUtil.java
@@ -19,6 +19,7 @@
 package org.apache.sentry.provider.db.log.util;
 
 import java.util.Iterator;
+import java.util.Set;
 
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.provider.db.service.thrift.TAlterSentryRoleAddGroupsRequest;
@@ -87,14 +88,25 @@ public class CommandUtil {
 
   public static String createCmdForGrantPrivilege(
       TAlterSentryRoleGrantPrivilegeRequest request) {
-    return createCmdForGrantOrRevokePrivilege(request.getRoleName(),
-        request.getPrivilege(), true);
+    return createCmdForGrantOrRevokePrivileges(request.getRoleName(),
+        request.getPrivileges(), true);
   }
 
   public static String createCmdForRevokePrivilege(
       TAlterSentryRoleRevokePrivilegeRequest request) {
-    return createCmdForGrantOrRevokePrivilege(request.getRoleName(),
-        request.getPrivilege(), false);
+    return createCmdForGrantOrRevokePrivileges(request.getRoleName(),
+        request.getPrivileges(), false);
+  }
+
+  private static String createCmdForGrantOrRevokePrivileges(String roleName,
+      Set<TSentryPrivilege> privileges, boolean isGrant) {
+    StringBuilder sb = new StringBuilder();
+    if (privileges != null) {
+      for (TSentryPrivilege privilege : privileges) {
+        sb.append(createCmdForGrantOrRevokePrivilege(roleName, privilege, isGrant));
+      }
+    }
+    return sb.toString();
   }
 
   private static String createCmdForGrantOrRevokePrivilege(String roleName,

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/Constants.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/Constants.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/Constants.java
index 9f16b73..072a0e8 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/Constants.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/log/util/Constants.java
@@ -41,6 +41,7 @@ public class Constants {
   public final static String LOG_FIELD_ALLOWED = "allowed";
   public final static String LOG_FIELD_DATABASE_NAME = "databaseName";
   public final static String LOG_FIELD_TABLE_NAME = "tableName";
+  public final static String LOG_FIELD_COLUMN_NAME = "column";
   public final static String LOG_FIELD_RESOURCE_PATH = "resourcePath";
   public final static String LOG_FIELD_OBJECT_TYPE = "objectType";
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
index 1150e47..3c8777c 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/MSentryPrivilege.java
@@ -41,6 +41,7 @@ public class MSentryPrivilege {
   private String serverName = "";
   private String dbName = "";
   private String tableName = "";
+  private String columnName = "";
   private String URI = "";
   private String action = "";
   private Boolean grantOption = false;
@@ -53,12 +54,13 @@ public class MSentryPrivilege {
   }
 
   public MSentryPrivilege(String privilegeName, String privilegeScope,
-      String serverName, String dbName, String tableName, String URI,
-      String action, Boolean grantOption) {
+      String serverName, String dbName, String tableName, String columnName,
+      String URI, String action, Boolean grantOption) {
     this.privilegeScope = privilegeScope;
     this.serverName = serverName;
     this.dbName = SentryStore.toNULLCol(dbName);
     this.tableName = SentryStore.toNULLCol(tableName);
+    this.columnName = SentryStore.toNULLCol(columnName);
     this.URI = SentryStore.toNULLCol(URI);
     this.action = SentryStore.toNULLCol(action);
     this.grantOption = grantOption;
@@ -66,10 +68,10 @@ public class MSentryPrivilege {
   }
 
   public MSentryPrivilege(String privilegeName, String privilegeScope,
-      String serverName, String dbName, String tableName, String URI,
-      String action) {
+      String serverName, String dbName, String tableName, String columnName,
+      String URI, String action) {
     this(privilegeName, privilegeScope, serverName, dbName, tableName,
-        URI, action, false);
+        columnName, URI, action, false);
   }
 
   public MSentryPrivilege(MSentryPrivilege other) {
@@ -77,6 +79,7 @@ public class MSentryPrivilege {
     this.serverName = other.serverName;
     this.dbName = SentryStore.toNULLCol(other.dbName);
     this.tableName = SentryStore.toNULLCol(other.tableName);
+    this.columnName = SentryStore.toNULLCol(other.columnName);
     this.URI = SentryStore.toNULLCol(other.URI);
     this.action = SentryStore.toNULLCol(other.action);
     this.grantOption = other.grantOption;
@@ -110,6 +113,14 @@ public class MSentryPrivilege {
     this.tableName = (tableName == null) ? "" : tableName;
   }
 
+  public String getColumnName() {
+    return columnName;
+  }
+
+  public void setColumnName(String columnName) {
+    this.columnName = (columnName == null) ? "" : columnName;
+  }
+
   public String getURI() {
     return URI;
   }
@@ -167,67 +178,74 @@ public class MSentryPrivilege {
   public String toString() {
     return "MSentryPrivilege [privilegeScope=" + privilegeScope
         + ", serverName=" + serverName + ", dbName=" + dbName
-        + ", tableName=" + tableName + ", URI=" + URI
-        + ", action=" + action + ", roles=[...]" + ", createTime="
-        + createTime
-        + ", grantOption=" + grantOption +"]";
+        + ", tableName=" + tableName + ", columnName=" + columnName
+        + ", URI=" + URI + ", action=" + action + ", roles=[...]"
+        + ", createTime=" + createTime + ", grantOption=" + grantOption +"]";
   }
 
-@Override
-public int hashCode() {
-  final int prime = 31;
-  int result = 1;
-  result = prime * result + ((URI == null) ? 0 : URI.hashCode());
-  result = prime * result + ((action == null) ? 0 : action.hashCode());
-  result = prime * result + ((dbName == null) ? 0 : dbName.hashCode());
-  result = prime * result
-		+ ((serverName == null) ? 0 : serverName.hashCode());
-  result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
-  result = prime * result + ((grantOption == null) ? 0 : grantOption.hashCode());
-  return result;
-}
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((URI == null) ? 0 : URI.hashCode());
+    result = prime * result + ((action == null) ? 0 : action.hashCode());
+    result = prime * result + ((dbName == null) ? 0 : dbName.hashCode());
+    result = prime * result
+        + ((serverName == null) ? 0 : serverName.hashCode());
+    result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
+    result = prime * result
+        + ((columnName == null) ? 0 : columnName.hashCode());
+    result = prime * result
+        + ((grantOption == null) ? 0 : grantOption.hashCode());
+    return result;
+  }
 
-@Override
-public boolean equals(Object obj) {
-	if (this == obj)
-		return true;
-	if (obj == null)
-		return false;
-	if (getClass() != obj.getClass())
-		return false;
-	MSentryPrivilege other = (MSentryPrivilege) obj;
-	if (URI == null) {
-		if (other.URI != null)
-			return false;
-	} else if (!URI.equals(other.URI))
-		return false;
-	if (action == null) {
-		if (other.action != null)
-			return false;
-	} else if (!action.equals(other.action))
-		return false;
-	if (dbName == null) {
-		if (other.dbName != null)
-			return false;
-	} else if (!dbName.equals(other.dbName))
-		return false;
-	if (serverName == null) {
-		if (other.serverName != null)
-			return false;
-	} else if (!serverName.equals(other.serverName))
-		return false;
-	if (tableName == null) {
-		if (other.tableName != null)
-			return false;
-	} else if (!tableName.equals(other.tableName))
-		return false;
-	if (grantOption == null) {
-	  if (other.grantOption != null)
-	    return false;
-	} else if (!grantOption.equals(other.grantOption))
-	  return false;
-	return true;
-}
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    MSentryPrivilege other = (MSentryPrivilege) obj;
+    if (URI == null) {
+      if (other.URI != null)
+        return false;
+    } else if (!URI.equals(other.URI))
+      return false;
+    if (action == null) {
+      if (other.action != null)
+        return false;
+    } else if (!action.equals(other.action))
+      return false;
+    if (dbName == null) {
+      if (other.dbName != null)
+        return false;
+    } else if (!dbName.equals(other.dbName))
+      return false;
+    if (serverName == null) {
+      if (other.serverName != null)
+        return false;
+    } else if (!serverName.equals(other.serverName))
+      return false;
+    if (tableName == null) {
+      if (other.tableName != null)
+        return false;
+    } else if (!tableName.equals(other.tableName))
+      return false;
+    if (columnName == null) {
+      if (other.columnName != null)
+        return false;
+    } else if (!columnName.equals(other.columnName))
+      return false;
+    if (grantOption == null) {
+      if (other.grantOption != null)
+        return false;
+    } else if (!grantOption.equals(other.grantOption))
+      return false;
+    return true;
+  }
 
   /**
    * Return true if this privilege implies other privilege
@@ -263,6 +281,13 @@ public boolean equals(Object obj) {
           return false;
         }
       }
+      if (!isNULL(columnName)) {
+        if (isNULL(other.columnName)) {
+          return false;
+        } else if (!columnName.equals(other.columnName)) {
+          return false;
+        }
+      }
       // if URI is not equals, return false
     } else {
       return false;

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
index 9abaab8..594201f 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/model/package.jdo
@@ -85,6 +85,7 @@
         <field name="serverName"/>
         <field name="dbName"/>
         <field name="tableName"/>
+        <field name="columnName"/>
         <field name="URI"/>
         <field name="action"/>
         <field name="grantOption"/>
@@ -101,6 +102,9 @@
       <field name="tableName">  
         <column name="TABLE_NAME" length="4000" jdbc-type="VARCHAR"/>
       </field>
+      <field name="columnName">
+        <column name="COLUMN_NAME" length="4000" jdbc-type="VARCHAR"/>
+      </field>
       <field name="URI">  
         <column name="URI" length="4000" jdbc-type="VARCHAR"/>
       </field>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
index 743900b..d163418 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
@@ -318,19 +318,27 @@ public class SentryStore {
   public CommitContext alterSentryRoleGrantPrivilege(String grantorPrincipal,
       String roleName, TSentryPrivilege privilege)
       throws SentryUserException {
+    return alterSentryRoleGrantPrivileges(grantorPrincipal,
+        roleName, Sets.newHashSet(privilege));
+  }
+
+  public CommitContext alterSentryRoleGrantPrivileges(String grantorPrincipal,
+      String roleName, Set<TSentryPrivilege> privileges)
+      throws SentryUserException {
     boolean rollbackTransaction = true;
     PersistenceManager pm = null;
     roleName = trimAndLower(roleName);
     try {
       pm = openTransaction();
-      // first do grant check
-      grantOptionCheck(pm, grantorPrincipal, privilege);
+      for (TSentryPrivilege privilege : privileges) {
+        // first do grant check
+        grantOptionCheck(pm, grantorPrincipal, privilege);
+
+        MSentryPrivilege mPrivilege = alterSentryRoleGrantPrivilegeCore(pm, roleName, privilege);
 
-      MSentryPrivilege mPrivilege =
-          alterSentryRoleGrantPrivilegeCore(pm, roleName, privilege);
-      // capture the new privilege
-      if (mPrivilege != null) {
-        convertToTSentryPrivilege(mPrivilege, privilege);
+        if (mPrivilege != null) {
+          convertToTSentryPrivilege(mPrivilege, privilege);
+        }
       }
       CommitContext commit = commitUpdateTransaction(pm);
       rollbackTransaction = false;
@@ -351,7 +359,8 @@ public class SentryStore {
       throw new SentryNoSuchObjectException("Role: " + roleName);
     } else {
 
-      if ((!isNULL(privilege.getTableName())) || (!isNULL(privilege.getDbName()))) {
+      if ((!isNULL(privilege.getColumnName())) || (!isNULL(privilege.getTableName()))
+          || (!isNULL(privilege.getDbName()))) {
         // If Grant is for ALL and Either INSERT/SELECT already exists..
         // need to remove it and GRANT ALL..
         if (privilege.getAction().equalsIgnoreCase("*")) {
@@ -391,17 +400,25 @@ public class SentryStore {
     return mPrivilege;
   }
 
-  public CommitContext alterSentryRoleRevokePrivilege(String grantorPrincipal, String roleName,
-      TSentryPrivilege tPrivilege) throws SentryUserException {
+  public CommitContext alterSentryRoleRevokePrivilege(String grantorPrincipal,
+      String roleName, TSentryPrivilege tPrivilege) throws SentryUserException {
+    return alterSentryRoleRevokePrivileges(grantorPrincipal,
+        roleName, Sets.newHashSet(tPrivilege));
+  }
+
+  public CommitContext alterSentryRoleRevokePrivileges(String grantorPrincipal,
+      String roleName, Set<TSentryPrivilege> tPrivileges) throws SentryUserException {
     boolean rollbackTransaction = true;
     PersistenceManager pm = null;
     roleName = safeTrimLower(roleName);
     try {
       pm = openTransaction();
-      // first do revoke check
-      grantOptionCheck(pm, grantorPrincipal, tPrivilege);
+      for (TSentryPrivilege tPrivilege : tPrivileges) {
+        // first do revoke check
+        grantOptionCheck(pm, grantorPrincipal, tPrivilege);
 
-      alterSentryRoleRevokePrivilegeCore(pm, roleName, tPrivilege);
+        alterSentryRoleRevokePrivilegeCore(pm, roleName, tPrivilege);
+      }
 
       CommitContext commit = commitUpdateTransaction(pm);
       rollbackTransaction = false;
@@ -508,12 +525,14 @@ public class SentryStore {
    */
   private void populateChildren(Set<String> roleNames, MSentryPrivilege priv,
       Set<MSentryPrivilege> children) throws SentryInvalidInputException {
-    if ((!isNULL(priv.getServerName())) || (!isNULL(priv.getDbName()))) {
-      // Get all DBLevel Privs
+    if ((!isNULL(priv.getServerName())) || (!isNULL(priv.getDbName()))
+        || (!isNULL(priv.getTableName()))) {
+      // Get all TableLevel Privs
       Set<MSentryPrivilege> childPrivs = getChildPrivileges(roleNames, priv);
       for (MSentryPrivilege childPriv : childPrivs) {
-        // Only recurse for db level privs..
-        if ((!isNULL(childPriv.getDbName())) && (!isNULL(childPriv.getTableName()))) {
+        // Only recurse for table level privs..
+        if ((!isNULL(childPriv.getDbName())) && (!isNULL(childPriv.getTableName()))
+            && (!isNULL(childPriv.getColumnName()))) {
           populateChildren(roleNames, childPriv, children);
         }
         children.add(childPriv);
@@ -523,8 +542,8 @@ public class SentryStore {
 
   private Set<MSentryPrivilege> getChildPrivileges(Set<String> roleNames,
       MSentryPrivilege parent) throws SentryInvalidInputException {
-    // Table and URI do not have children
-    if ((!isNULL(parent.getTableName()))||(!isNULL(parent.getURI()))) return new HashSet<MSentryPrivilege>();
+    // Column and URI do not have children
+    if ((!isNULL(parent.getColumnName()))||(!isNULL(parent.getURI()))) return new HashSet<MSentryPrivilege>();
     boolean rollbackTransaction = true;
     PersistenceManager pm = null;
     try {
@@ -541,14 +560,20 @@ public class SentryStore {
       filters.append(" && serverName == \"" + parent.getServerName() + "\"");
       if (!isNULL(parent.getDbName())) {
         filters.append(" && dbName == \"" + parent.getDbName() + "\"");
-        filters.append(" && tableName != \"__NULL__\"");
+        if (!isNULL(parent.getTableName())) {
+          filters.append(" && tableName == \"" + parent.getTableName() + "\"");
+          filters.append(" && columnName != \"__NULL__\"");
+        } else {
+          filters.append(" && tableName != \"__NULL__\"");
+        }
       } else {
         filters.append(" && (dbName != \"__NULL__\" || URI != \"__NULL__\")");
       }
 
       query.setFilter(filters.toString());
       query
-          .setResult("privilegeScope, serverName, dbName, tableName, URI, action, grantOption");
+          .setResult("privilegeScope, serverName, dbName, tableName, columnName," +
+          		" URI, action, grantOption");
       Set<MSentryPrivilege> privileges = new HashSet<MSentryPrivilege>();
       for (Object[] privObj : (List<Object[]>) query.execute()) {
         MSentryPrivilege priv = new MSentryPrivilege();
@@ -556,9 +581,10 @@ public class SentryStore {
         priv.setServerName((String) privObj[1]);
         priv.setDbName((String) privObj[2]);
         priv.setTableName((String) privObj[3]);
-        priv.setURI((String) privObj[4]);
-        priv.setAction((String) privObj[5]);
-        priv.setGrantOption((Boolean) privObj[6]);
+        priv.setColumnName((String) privObj[4]);
+        priv.setURI((String) privObj[5]);
+        priv.setAction((String) privObj[6]);
+        priv.setGrantOption((Boolean) privObj[7]);
         privileges.add(priv);
       }
       rollbackTransaction = false;
@@ -571,11 +597,35 @@ public class SentryStore {
     }
   }
 
+  private List<MSentryPrivilege> getMSentryPrivileges(TSentryPrivilege tPriv, PersistenceManager pm) {
+    Query query = pm.newQuery(MSentryPrivilege.class);
+    StringBuilder filters = new StringBuilder("this.serverName == \"" + toNULLCol(tPriv.getServerName()) + "\" ");
+    if (!isNULL(tPriv.getDbName())) {
+      filters.append("&& this.dbName == \"" + toNULLCol(tPriv.getDbName()) + "\" ");
+      if (!isNULL(tPriv.getTableName())) {
+        filters.append("&& this.tableName == \"" + toNULLCol(tPriv.getTableName()) + "\" ");
+        if (!isNULL(tPriv.getColumnName())) {
+          filters.append("&& this.columnName == \"" + toNULLCol(tPriv.getColumnName()) + "\" ");
+        }
+      }
+    }
+    // if db is null, uri is not null
+    else if (!isNULL(tPriv.getURI())){
+      filters.append("&& this.URI == \"" + toNULLCol(tPriv.getURI()) + "\" ");
+    }
+    filters.append("&& this.action == \"" + toNULLCol(tPriv.getAction().toLowerCase()) + "\"");
+
+    query.setFilter(filters.toString());
+    List<MSentryPrivilege> privileges = (List<MSentryPrivilege>) query.execute();
+    return privileges;
+  }
+
   private MSentryPrivilege getMSentryPrivilege(TSentryPrivilege tPriv, PersistenceManager pm) {
     Query query = pm.newQuery(MSentryPrivilege.class);
     query.setFilter("this.serverName == \"" + toNULLCol(tPriv.getServerName()) + "\" "
 				+ "&& this.dbName == \"" + toNULLCol(tPriv.getDbName()) + "\" "
 				+ "&& this.tableName == \"" + toNULLCol(tPriv.getTableName()) + "\" "
+				+ "&& this.columnName == \"" + toNULLCol(tPriv.getColumnName()) + "\" "
 				+ "&& this.URI == \"" + toNULLCol(tPriv.getURI()) + "\" "
 				+ "&& this.grantOption == grantOption "
 				+ "&& this.action == \"" + toNULLCol(tPriv.getAction().toLowerCase()) + "\"");
@@ -786,6 +836,11 @@ public class SentryStore {
               && !AccessConstants.ALL
                   .equalsIgnoreCase(authHierarchy.getTable())) {
             filters.append(" && ((tableName == \"" + authHierarchy.getTable().toLowerCase() + "\") || (tableName == \"__NULL__\")) && (URI == \"__NULL__\")");
+            if ((authHierarchy.getColumn() != null)
+                && !AccessConstants.ALL
+                    .equalsIgnoreCase(authHierarchy.getColumn())) {
+              filters.append(" && ((columnName == \"" + authHierarchy.getColumn().toLowerCase() + "\") || (columnName == \"__NULL__\")) && (URI == \"__NULL__\")");
+            }
           }
         }
         if (authHierarchy.getUri() != null) {
@@ -932,6 +987,9 @@ public class SentryStore {
     if ((authHierarchy.getTable() != null) && (authHierarchy.getDb() == null)) {
       throw new SentryInvalidInputException("dbName cannot be null when tableName is present !!");
     }
+    if ((authHierarchy.getColumn() != null) && (authHierarchy.getTable() == null)) {
+      throw new SentryInvalidInputException("tableName cannot be null when columnName is present !!");
+    }
     if ((authHierarchy.getUri() == null) && (authHierarchy.getDb() == null)) {
       throw new SentryInvalidInputException("One of uri or dbName must not be null !!");
     }
@@ -1091,6 +1149,10 @@ public class SentryStore {
         if (!isNULL(privilege.getTableName())) {
           authorizable.add(KV_JOINER.join(AuthorizableType.Table.name().toLowerCase(),
               privilege.getTableName()));
+          if (!isNULL(privilege.getColumnName())) {
+            authorizable.add(KV_JOINER.join(AuthorizableType.Column.name().toLowerCase(),
+                privilege.getColumnName()));
+          }
         }
       }
     } else {
@@ -1174,6 +1236,7 @@ public class SentryStore {
     privilege.setServerName(fromNULLCol(mSentryPrivilege.getServerName()));
     privilege.setDbName(fromNULLCol(mSentryPrivilege.getDbName()));
     privilege.setTableName(fromNULLCol(mSentryPrivilege.getTableName()));
+    privilege.setColumnName(fromNULLCol(mSentryPrivilege.getColumnName()));
     privilege.setURI(fromNULLCol(mSentryPrivilege.getURI()));
     if (mSentryPrivilege.getGrantOption() != null) {
       privilege.setGrantOption(TSentryGrantOption.valueOf(mSentryPrivilege.getGrantOption().toString().toUpperCase()));
@@ -1193,6 +1256,7 @@ public class SentryStore {
     mSentryPrivilege.setServerName(toNULLCol(safeTrimLower(privilege.getServerName())));
     mSentryPrivilege.setDbName(toNULLCol(safeTrimLower(privilege.getDbName())));
     mSentryPrivilege.setTableName(toNULLCol(safeTrimLower(privilege.getTableName())));
+    mSentryPrivilege.setColumnName(toNULLCol(safeTrimLower(privilege.getColumnName())));
     mSentryPrivilege.setPrivilegeScope(safeTrim(privilege.getPrivilegeScope()));
     mSentryPrivilege.setAction(toNULLCol(safeTrimLower(privilege.getAction())));
     mSentryPrivilege.setCreateTime(System.currentTimeMillis());
@@ -1400,14 +1464,37 @@ public class SentryStore {
       SentryInvalidInputException {
     HashSet<MSentryRole> roleSet = Sets.newHashSet();
 
-    MSentryPrivilege mPrivilege = getMSentryPrivilege(tPrivilege, pm);
-    if (mPrivilege != null) {
-      roleSet.addAll(ImmutableSet.copyOf((mPrivilege.getRoles())));
+    List<MSentryPrivilege> mPrivileges = getMSentryPrivileges(tPrivilege, pm);
+    if (mPrivileges != null && !mPrivileges.isEmpty()) {
+      for (MSentryPrivilege mPrivilege : mPrivileges) {
+        roleSet.addAll(ImmutableSet.copyOf((mPrivilege.getRoles())));
+      }
     }
+
+    MSentryPrivilege parent = getMSentryPrivilege(tPrivilege, pm);
     for (MSentryRole role : roleSet) {
+      // 1. get privilege and child privileges
+      Set<MSentryPrivilege> privilegeGraph = Sets.newHashSet();
+      if (parent != null) {
+        privilegeGraph.add(parent);
+        populateChildren(Sets.newHashSet(role.getRoleName()), parent, privilegeGraph);
+      } else {
+        populateChildren(Sets.newHashSet(role.getRoleName()), convertToMSentryPrivilege(tPrivilege),
+            privilegeGraph);
+      }
+      // 2. revoke privilege and child privileges
       alterSentryRoleRevokePrivilegeCore(pm, role.getRoleName(), tPrivilege);
+      // 3. add new privilege and child privileges with new tableName
       if (newTPrivilege != null) {
-        alterSentryRoleGrantPrivilegeCore(pm, role.getRoleName(), newTPrivilege);
+        for (MSentryPrivilege m : privilegeGraph) {
+          TSentryPrivilege t = convertToTSentryPrivilege(m);
+          if (newTPrivilege.getPrivilegeScope().equals(PrivilegeScope.DATABASE.name())) {
+            t.setDbName(newTPrivilege.getDbName());
+          } else if (newTPrivilege.getPrivilegeScope().equals(PrivilegeScope.TABLE.name())) {
+            t.setTableName(newTPrivilege.getTableName());
+          }
+          alterSentryRoleGrantPrivilegeCore(pm, role.getRoleName(), t);
+        }
       }
     }
   }
@@ -1418,9 +1505,12 @@ public class SentryStore {
     tSentryPrivilege.setDbName(fromNULLCol(tAuthorizable.getDb()));
     tSentryPrivilege.setServerName(fromNULLCol(tAuthorizable.getServer()));
     tSentryPrivilege.setTableName(fromNULLCol(tAuthorizable.getTable()));
+    tSentryPrivilege.setColumnName(fromNULLCol(tAuthorizable.getColumn()));
     tSentryPrivilege.setURI(fromNULLCol(tAuthorizable.getUri()));
     PrivilegeScope scope;
-    if (!isNULL(tSentryPrivilege.getTableName())) {
+    if (!isNULL(tSentryPrivilege.getColumnName())) {
+      scope = PrivilegeScope.COLUMN;
+    } else if (!isNULL(tSentryPrivilege.getTableName())) {
       scope = PrivilegeScope.TABLE;
     } else if (!isNULL(tSentryPrivilege.getDbName())) {
       scope = PrivilegeScope.DATABASE;

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
index 39371b7..f5a6c8a 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyServiceClient.java
@@ -55,6 +55,8 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -281,57 +283,88 @@ public class SentryPolicyServiceClient {
       String roleName, String server, String uri)
   throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL);
+        PrivilegeScope.URI, server, uri, null, null, null, AccessConstants.ALL);
   }
 
   public TSentryPrivilege grantURIPrivilege(String requestorUserName,
       String roleName, String server, String uri, Boolean grantOption)
   throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL, grantOption);
+        PrivilegeScope.URI, server, uri, null, null, null, AccessConstants.ALL, grantOption);
   }
 
   public void grantServerPrivilege(String requestorUserName,
       String roleName, String server, String action)
   throws SentryUserException {
     grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.SERVER, server, null, null, null, action);
+        PrivilegeScope.SERVER, server, null, null, null, null, action);
   }
 
   public TSentryPrivilege grantServerPrivilege(String requestorUserName,
       String roleName, String server, String action, Boolean grantOption)
   throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.SERVER, server, null, null, null, action, grantOption);
+        PrivilegeScope.SERVER, server, null, null, null, null, action, grantOption);
   }
 
   public TSentryPrivilege grantDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action)
   throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.DATABASE, server, null, db, null, action);
+        PrivilegeScope.DATABASE, server, null, db, null, null, action);
   }
 
   public TSentryPrivilege grantDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action, Boolean grantOption)
   throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName,
-        PrivilegeScope.DATABASE, server, null, db, null, action, grantOption);
+        PrivilegeScope.DATABASE, server, null, db, null, null, action, grantOption);
   }
 
   public TSentryPrivilege grantTablePrivilege(String requestorUserName,
       String roleName, String server, String db, String table, String action)
   throws SentryUserException {
-    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.TABLE,
-        server, null, db, table, action);
+    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.TABLE, server,
+        null,
+        db, table, null, action);
   }
 
   public TSentryPrivilege grantTablePrivilege(String requestorUserName,
       String roleName, String server, String db, String table, String action, Boolean grantOption)
   throws SentryUserException {
-    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.TABLE,
+    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.TABLE, server,
+        null, db, table, null, action, grantOption);
+  }
+
+  public TSentryPrivilege grantColumnPrivilege(String requestorUserName,
+      String roleName, String server, String db, String table, String columnName, String action)
+  throws SentryUserException {
+    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.COLUMN, server,
+          null,
+          db, table, columnName, action);
+  }
+
+  public TSentryPrivilege grantColumnPrivilege(String requestorUserName,
+      String roleName, String server, String db, String table, String columnName, String action, Boolean grantOption)
+  throws SentryUserException {
+    return grantPrivilege(requestorUserName, roleName, PrivilegeScope.COLUMN, server,
+          null, db, table, columnName, action, grantOption);
+  }
+
+  public Set<TSentryPrivilege> grantColumnsPrivileges(String requestorUserName,
+      String roleName, String server, String db, String table, List<String> columnNames, String action)
+  throws SentryUserException {
+    return grantPrivileges(requestorUserName, roleName, PrivilegeScope.COLUMN, server,
+            null,
+            db, table, columnNames, action);
+  }
+
+  public Set<TSentryPrivilege> grantColumnsPrivileges(String requestorUserName,
+      String roleName, String server, String db, String table, List<String> columnNames, String action, Boolean grantOption)
+  throws SentryUserException {
+    return grantPrivileges(requestorUserName, roleName, PrivilegeScope.COLUMN,
         server,
-        null, db, table, action, grantOption);
+        null, db, table, columnNames, action, grantOption);
   }
 
   @VisibleForTesting
@@ -352,6 +385,9 @@ public class SentryPolicyServiceClient {
       } else if (authzble.getTypeName().equalsIgnoreCase(
           DBModelAuthorizable.AuthorizableType.Table.toString())) {
         tSentryAuthorizable.setTable(authzble.getName());
+      } else if (authzble.getTypeName().equalsIgnoreCase(
+          DBModelAuthorizable.AuthorizableType.Column.toString())) {
+        tSentryAuthorizable.setColumn(authzble.getName());
       }
     }
     return tSentryAuthorizable;
@@ -360,32 +396,59 @@ public class SentryPolicyServiceClient {
   private TSentryPrivilege grantPrivilege(String requestorUserName,
       String roleName,
       PrivilegeScope scope, String serverName, String uri, String db,
-      String table, String action)  throws SentryUserException {
+      String table, String column, String action)  throws SentryUserException {
     return grantPrivilege(requestorUserName, roleName, scope, serverName, uri,
-    db, table, action, false);
+    db, table, column, action, false);
   }
 
   private TSentryPrivilege grantPrivilege(String requestorUserName,
-      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table, String action, Boolean grantOption)
+      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table,
+      String column, String action, Boolean grantOption)
   throws SentryUserException {
     TAlterSentryRoleGrantPrivilegeRequest request = new TAlterSentryRoleGrantPrivilegeRequest();
     request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
     request.setRequestorUserName(requestorUserName);
     request.setRoleName(roleName);
-    TSentryPrivilege privilege = new TSentryPrivilege();
-    privilege.setPrivilegeScope(scope.toString());
-    privilege.setServerName(serverName);
-    privilege.setURI(uri);
-    privilege.setDbName(db);
-    privilege.setTableName(table);
-    privilege.setAction(action);
-    privilege.setCreateTime(System.currentTimeMillis());
-    privilege.setGrantOption(convertTSentryGrantOption(grantOption));
-    request.setPrivilege(privilege);
+    Set<TSentryPrivilege> privileges = convertColumnPrivilege(requestorUserName, scope,
+        serverName, uri, db, table, column, action, grantOption);
+    request.setPrivileges(privileges);
     try {
       TAlterSentryRoleGrantPrivilegeResponse response = client.alter_sentry_role_grant_privilege(request);
       Status.throwIfNotOk(response.getStatus());
-      return response.getPrivilege();
+      if (response.isSetPrivileges()
+          && response.getPrivilegesSize()>0 ) {
+        return response.getPrivileges().iterator().next();
+      } else {
+        return new TSentryPrivilege();
+      }
+    } catch (TException e) {
+      throw new SentryUserException(THRIFT_EXCEPTION_MESSAGE, e);
+    }
+  }
+
+  private Set<TSentryPrivilege> grantPrivileges(String requestorUserName,
+      String roleName,
+      PrivilegeScope scope, String serverName, String uri, String db,
+      String table, List<String> columns, String action)  throws SentryUserException {
+    return grantPrivileges(requestorUserName, roleName, scope, serverName, uri,
+    db, table, columns, action, false);
+  }
+
+  private Set<TSentryPrivilege> grantPrivileges(String requestorUserName,
+      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table,
+      List<String> columns, String action, Boolean grantOption)
+  throws SentryUserException {
+    TAlterSentryRoleGrantPrivilegeRequest request = new TAlterSentryRoleGrantPrivilegeRequest();
+    request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
+    request.setRequestorUserName(requestorUserName);
+    request.setRoleName(roleName);
+    Set<TSentryPrivilege> privileges = convertColumnPrivileges(requestorUserName, scope,
+        serverName, uri, db, table, columns, action, grantOption);
+    request.setPrivileges(privileges);
+    try {
+      TAlterSentryRoleGrantPrivilegeResponse response = client.alter_sentry_role_grant_privilege(request);
+      Status.throwIfNotOk(response.getStatus());
+      return response.getPrivileges();
     } catch (TException e) {
       throw new SentryUserException(THRIFT_EXCEPTION_MESSAGE, e);
     }
@@ -395,42 +458,42 @@ public class SentryPolicyServiceClient {
       String roleName, String server, String uri)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL);
+        PrivilegeScope.URI, server, uri, null, null, null, AccessConstants.ALL);
   }
 
   public void revokeURIPrivilege(String requestorUserName,
       String roleName, String server, String uri, Boolean grantOption)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.URI, server, uri, null, null, AccessConstants.ALL, grantOption);
+        PrivilegeScope.URI, server, uri, null, null, null, AccessConstants.ALL, grantOption);
   }
 
   public void revokeServerPrivilege(String requestorUserName,
       String roleName, String server)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL);
+        PrivilegeScope.SERVER, server, null, null, null, null, AccessConstants.ALL);
   }
 
   public void revokeServerPrivilege(String requestorUserName,
       String roleName, String server, Boolean grantOption)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.SERVER, server, null, null, null, AccessConstants.ALL, grantOption);
+        PrivilegeScope.SERVER, server, null, null, null, null, AccessConstants.ALL, grantOption);
   }
 
   public void revokeDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.DATABASE, server, null, db, null, action);
+        PrivilegeScope.DATABASE, server, null, db, null, null, action);
   }
 
   public void revokeDatabasePrivilege(String requestorUserName,
       String roleName, String server, String db, String action, Boolean grantOption)
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
-        PrivilegeScope.DATABASE, server, null, db, null, action, grantOption);
+        PrivilegeScope.DATABASE, server, null, db, null, null, action, grantOption);
   }
 
   public void revokeTablePrivilege(String requestorUserName,
@@ -438,7 +501,7 @@ public class SentryPolicyServiceClient {
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
         PrivilegeScope.TABLE, server, null,
-        db, table, action);
+        db, table, null, action);
   }
 
   public void revokeTablePrivilege(String requestorUserName,
@@ -446,38 +509,121 @@ public class SentryPolicyServiceClient {
   throws SentryUserException {
     revokePrivilege(requestorUserName, roleName,
         PrivilegeScope.TABLE, server, null,
-        db, table, action, grantOption);
+        db, table, null, action, grantOption);
+  }
+
+  public void revokeColumnPrivilege(String requestorUserName, String roleName,
+      String server, String db, String table, String columnName, String action)
+  throws SentryUserException {
+    ImmutableList.Builder<String> listBuilder = ImmutableList.builder();
+    listBuilder.add(columnName);
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.TABLE, server, null,
+        db, table, listBuilder.build(), action);
+  }
+
+  public void revokeColumnPrivilege(String requestorUserName, String roleName,
+      String server, String db, String table, String columnName, String action, Boolean grantOption)
+  throws SentryUserException {
+    ImmutableList.Builder<String> listBuilder = ImmutableList.builder();
+    listBuilder.add(columnName);
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.TABLE, server, null,
+        db, table, listBuilder.build(), action, grantOption);
+  }
+
+  public void revokeColumnsPrivilege(String requestorUserName, String roleName,
+      String server, String db, String table, List<String> columns, String action)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.TABLE, server, null,
+        db, table, columns, action);
+  }
+
+  public void revokeColumnsPrivilege(String requestorUserName, String roleName,
+      String server, String db, String table, List<String> columns, String action, Boolean grantOption)
+  throws SentryUserException {
+    revokePrivilege(requestorUserName, roleName,
+        PrivilegeScope.TABLE, server, null,
+        db, table, columns, action, grantOption);
   }
 
   private void revokePrivilege(String requestorUserName,
-      String roleName, PrivilegeScope scope, String serverName, String uri, String db, String table, String action)
+      String roleName, PrivilegeScope scope, String serverName, String uri,
+      String db, String table, List<String> columns, String action)
   throws SentryUserException {
-    this.revokePrivilege(requestorUserName, roleName, scope, serverName, uri, db, table, action, false);
+    this.revokePrivilege(requestorUserName, roleName, scope, serverName, uri, db, table, columns, action, false);
   }
 
   private void revokePrivilege(String requestorUserName, String roleName,
-      PrivilegeScope scope, String serverName, String uri, String db, String table, String action, Boolean grantOption)
+      PrivilegeScope scope, String serverName, String uri, String db, String table, List<String> columns,
+      String action, Boolean grantOption)
   throws SentryUserException {
     TAlterSentryRoleRevokePrivilegeRequest request = new TAlterSentryRoleRevokePrivilegeRequest();
     request.setProtocol_version(ThriftConstants.TSENTRY_SERVICE_VERSION_CURRENT);
     request.setRequestorUserName(requestorUserName);
     request.setRoleName(roleName);
+    Set<TSentryPrivilege> privileges = convertColumnPrivileges(requestorUserName, scope,
+        serverName, uri, db, table, columns, action, grantOption);
+    request.setPrivileges(privileges);
+    try {
+      TAlterSentryRoleRevokePrivilegeResponse response = client.alter_sentry_role_revoke_privilege(request);
+      Status.throwIfNotOk(response.getStatus());
+    } catch (TException e) {
+      throw new SentryUserException(THRIFT_EXCEPTION_MESSAGE, e);
+    }
+  }
+
+  private Set<TSentryPrivilege> convertColumnPrivileges(String requestorUserName,
+      PrivilegeScope scope, String serverName, String uri, String db, String table, List<String> columns,
+      String action, Boolean grantOption) {
+    ImmutableSet.Builder<TSentryPrivilege> setBuilder = ImmutableSet.builder();
+    if (columns == null || columns.isEmpty()) {
+      TSentryPrivilege privilege = new TSentryPrivilege();
+      privilege.setPrivilegeScope(scope.toString());
+      privilege.setServerName(serverName);
+      privilege.setURI(uri);
+      privilege.setDbName(db);
+      privilege.setTableName(table);
+      privilege.setColumnName(null);
+      privilege.setAction(action);
+      privilege.setCreateTime(System.currentTimeMillis());
+      privilege.setGrantOption(convertTSentryGrantOption(grantOption));
+      setBuilder.add(privilege);
+    } else {
+      for (String column : columns) {
+        TSentryPrivilege privilege = new TSentryPrivilege();
+        privilege.setPrivilegeScope(scope.toString());
+        privilege.setServerName(serverName);
+        privilege.setURI(uri);
+        privilege.setDbName(db);
+        privilege.setTableName(table);
+        privilege.setColumnName(column);
+        privilege.setAction(action);
+        privilege.setCreateTime(System.currentTimeMillis());
+        privilege.setGrantOption(convertTSentryGrantOption(grantOption));
+        setBuilder.add(privilege);
+      }
+    }
+    return setBuilder.build();
+  }
+
+  private Set<TSentryPrivilege> convertColumnPrivilege(String requestorUserName,
+      PrivilegeScope scope, String serverName, String uri, String db, String table, String column,
+      String action, Boolean grantOption) {
+    ImmutableSet.Builder<TSentryPrivilege> setBuilder = ImmutableSet.builder();
     TSentryPrivilege privilege = new TSentryPrivilege();
     privilege.setPrivilegeScope(scope.toString());
     privilege.setServerName(serverName);
     privilege.setURI(uri);
     privilege.setDbName(db);
     privilege.setTableName(table);
+    privilege.setColumnName(column);
     privilege.setAction(action);
     privilege.setCreateTime(System.currentTimeMillis());
     privilege.setGrantOption(convertTSentryGrantOption(grantOption));
-    request.setPrivilege(privilege);
-    try {
-      TAlterSentryRoleRevokePrivilegeResponse response = client.alter_sentry_role_revoke_privilege(request);
-      Status.throwIfNotOk(response.getStatus());
-    } catch (TException e) {
-      throw new SentryUserException(THRIFT_EXCEPTION_MESSAGE, e);
-    }
+    setBuilder.add(privilege);
+    return setBuilder.build();
   }
 
   private TSentryGrantOption convertTSentryGrantOption(Boolean grantOption) {

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
index 4774b90..c3dbf78 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/thrift/SentryPolicyStoreProcessor.java
@@ -47,6 +47,7 @@ import org.apache.sentry.provider.db.SentryInvalidInputException;
 import org.apache.sentry.provider.db.SentryNoSuchObjectException;
 import org.apache.sentry.provider.db.SentryPolicyStorePlugin;
 import org.apache.sentry.provider.db.SentryPolicyStorePlugin.SentryPluginException;
+import org.apache.sentry.provider.db.log.entity.JsonLogEntity;
 import org.apache.sentry.provider.db.log.entity.JsonLogEntityFactory;
 import org.apache.sentry.provider.db.log.util.Constants;
 import org.apache.sentry.provider.db.service.persistent.CommitContext;
@@ -55,6 +56,7 @@ import org.apache.sentry.provider.db.service.thrift.PolicyStoreConstants.PolicyS
 import org.apache.sentry.service.thrift.ServiceConstants.ConfUtilties;
 import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
 import org.apache.sentry.service.thrift.ProcessorFactory;
+import org.apache.sentry.service.thrift.ServiceConstants.ThriftConstants;
 import org.apache.sentry.service.thrift.Status;
 import org.apache.sentry.service.thrift.TSentryResponseStatus;
 import org.apache.thrift.TException;
@@ -245,10 +247,22 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
 
     TAlterSentryRoleGrantPrivilegeResponse response = new TAlterSentryRoleGrantPrivilegeResponse();
     try {
-      CommitContext commitContext = sentryStore.alterSentryRoleGrantPrivilege(request.getRequestorUserName(),
-          request.getRoleName(), request.getPrivilege());
+      // There should only one field be set
+      if ( !(request.isSetPrivileges()^request.isSetPrivilege()) ) {
+        throw new SentryUserException("SENTRY API version is not right!");
+      }
+      // Maintain compatibility for old API: Set privilege field to privileges field
+      if (request.isSetPrivilege()) {
+        request.setPrivileges(Sets.newHashSet(request.getPrivilege()));
+      }
+      CommitContext commitContext = sentryStore.alterSentryRoleGrantPrivileges(request.getRequestorUserName(),
+          request.getRoleName(), request.getPrivileges());
       response.setStatus(Status.OK());
-      response.setPrivilege(request.getPrivilege());
+      response.setPrivileges(request.getPrivileges());
+      // Maintain compatibility for old API: Set privilege field to response
+      if (response.isSetPrivileges() && response.getPrivileges().size() == 1) {
+        response.setPrivilege(response.getPrivileges().iterator().next());
+      }
       notificationHandlerInvoker.alter_sentry_role_grant_privilege(commitContext,
           request, response);
       for (SentryPolicyStorePlugin plugin : sentryPlugins) {
@@ -273,8 +287,11 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
       timerContext.stop();
     }
 
-    AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance().createJsonLogEntity(
-    		request, response, conf).toJsonFormatLog());
+    Set<JsonLogEntity> jsonLogEntitys = JsonLogEntityFactory.getInstance().createJsonLogEntitys(
+        request, response, conf);
+    for (JsonLogEntity jsonLogEntity : jsonLogEntitys) {
+      AUDIT_LOGGER.info(jsonLogEntity.toJsonFormatLog());
+    }
     return response;
   }
 
@@ -284,8 +301,16 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
     final Timer.Context timerContext = sentryMetrics.revokeTimer.time();
     TAlterSentryRoleRevokePrivilegeResponse response = new TAlterSentryRoleRevokePrivilegeResponse();
     try {
-      CommitContext commitContext = sentryStore.alterSentryRoleRevokePrivilege(request.getRequestorUserName(),
-          request.getRoleName(), request.getPrivilege());
+      // There should only one field be set
+      if ( !(request.isSetPrivileges()^request.isSetPrivilege()) ) {
+        throw new SentryUserException("SENTRY API version is not right!");
+      }
+      // Maintain compatibility for old API: Set privilege field to privileges field
+      if (request.isSetPrivilege()) {
+        request.setPrivileges(Sets.newHashSet(request.getPrivilege()));
+      }
+      CommitContext commitContext = sentryStore.alterSentryRoleRevokePrivileges(request.getRequestorUserName(),
+          request.getRoleName(), request.getPrivileges());
       response.setStatus(Status.OK());
       notificationHandlerInvoker.alter_sentry_role_revoke_privilege(commitContext,
           request, response);
@@ -293,13 +318,25 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
         plugin.onAlterSentryRoleRevokePrivilege(request);
       }
     } catch (SentryNoSuchObjectException e) {
-      String msg = "Privilege: [server=" + request.getPrivilege().getServerName() +
-              ",db=" + request.getPrivilege().getDbName() +
-              ",table=" + request.getPrivilege().getTableName() +
-              ",URI=" + request.getPrivilege().getURI() +
-              ",action=" + request.getPrivilege().getAction() + "] doesn't exist.";
-      LOGGER.error(msg, e);
-      response.setStatus(Status.NoSuchObject(msg, e));
+      StringBuilder msg = new StringBuilder();
+      if (request.getPrivileges().size() > 0) {
+        for (TSentryPrivilege privilege : request.getPrivileges()) {
+          msg.append("Privilege: [server=");
+          msg.append(privilege.getServerName());
+          msg.append(",db=");
+          msg.append(privilege.getDbName());
+          msg.append(",table=");
+          msg.append(privilege.getTableName());
+          msg.append(",URI=");
+          msg.append(privilege.getURI());
+          msg.append(",action=");
+          msg.append(privilege.getAction());
+          msg.append("] ");
+        }
+        msg.append("doesn't exist.");
+      }
+      LOGGER.error(msg.toString(), e);
+      response.setStatus(Status.NoSuchObject(msg.toString(), e));
     } catch (SentryInvalidInputException e) {
       String msg = "Invalid input privilege object";
       LOGGER.error(msg, e);
@@ -315,8 +352,11 @@ public class SentryPolicyStoreProcessor implements SentryPolicyService.Iface {
       timerContext.stop();
     }
 
-    AUDIT_LOGGER.info(JsonLogEntityFactory.getInstance().createJsonLogEntity(
-    		request, response, conf).toJsonFormatLog());
+    Set<JsonLogEntity> jsonLogEntitys = JsonLogEntityFactory.getInstance().createJsonLogEntitys(
+        request, response, conf);
+    for (JsonLogEntity jsonLogEntity : jsonLogEntitys) {
+      AUDIT_LOGGER.info(jsonLogEntity.toJsonFormatLog());
+    }
     return response;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.derby.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.derby.sql b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.derby.sql
new file mode 100644
index 0000000..b82e97f
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.derby.sql
@@ -0,0 +1,4 @@
+-- SENTRY-74
+ALTER TABLE SENTRY_DB_PRIVILEGE ADD COLUMN COLUMN_NAME VARCHAR(4000);
+DROP INDEX SENTRYPRIVILEGENAME;
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME","COLUMN_NAME",URI,"ACTION",WITH_GRANT_OPTION);

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.mysql.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.mysql.sql b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.mysql.sql
new file mode 100644
index 0000000..c475a2c
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.mysql.sql
@@ -0,0 +1,4 @@
+-- SENTRY-74
+ALTER TABLE `SENTRY_DB_PRIVILEGE` ADD `COLUMN_NAME` VARCHAR(128) DEFAULT NULL;
+ALTER TABLE `SENTRY_DB_PRIVILEGE` DROP INDEX `SENTRY_DB_PRIV_PRIV_NAME_UNIQ`;
+ALTER TABLE `SENTRY_DB_PRIVILEGE` ADD UNIQUE `SENTRY_DB_PRIV_PRIV_NAME_UNIQ` (`SERVER_NAME`,`DB_NAME`,`TABLE_NAME`,`COLUMN_NAME`,`URI`(250),`ACTION`,`WITH_GRANT_OPTION`);

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.oracle.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.oracle.sql b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.oracle.sql
new file mode 100644
index 0000000..a78b76f
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.oracle.sql
@@ -0,0 +1,4 @@
+-- SENTRY-74
+ALTER TABLE SENTRY_DB_PRIVILEGE ADD COLUMN_NAME VARCHAR2(128) DEFAULT NULL;
+ALTER TABLE SENTRY_DB_PRIVILEGE DROP CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ";
+ALTER TABLE SENTRY_DB_PRIVILEGE ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","COLUMN_NAME","URI","ACTION","WITH_GRANT_OPTION");

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.postgres.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.postgres.sql b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.postgres.sql
new file mode 100644
index 0000000..74ed9c3
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/resources/004-SENTRY-74.postgres.sql
@@ -0,0 +1,4 @@
+-- SENTRY-74
+ALTER TABLE SENTRY_DB_PRIVILEGE ADD COLUMN COLUMN_NAME character varying(128) DEFAULT NULL;
+ALTER TABLE SENTRY_DB_PRIVILEGE DROP CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ";
+ALTER TABLE SENTRY_DB_PRIVILEGE ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","COLUMN_NAME","URI", "ACTION","WITH_GRANT_OPTION");

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.5.0.sql
index 4c5ae39..171adec 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-db2-1.5.0.sql
@@ -24,6 +24,7 @@ CREATE TABLE SENTRY_DB_PRIVILEGE
     PRIVILEGE_SCOPE VARCHAR(40),
     "SERVER_NAME" VARCHAR(4000),
     "TABLE_NAME" VARCHAR(4000),
+    "COLUMN_NAME" VARCHAR(4000),
     WITH_GRANT_OPTION CHAR(1) NOT NULL
 );
 
@@ -78,7 +79,7 @@ CREATE TABLE "SENTRY_VERSION" (
 ALTER TABLE SENTRY_VERSION ADD CONSTRAINT SENTRY_VERSION_PK PRIMARY KEY (VER_ID);
 
 -- Constraints for table SENTRY_DB_PRIVILEGE for class(es) [org.apache.sentry.provider.db.service.model.MSentryPrivilege]
-CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION",WITH_GRANT_OPTION);
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME","COLUMN_NAME",URI,"ACTION",WITH_GRANT_OPTION);
 
 
 -- Constraints for table SENTRY_ROLE for class(es) [org.apache.sentry.provider.db.service.model.MSentryRole]

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.5.0.sql
index 4c5ae39..171adec 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-derby-1.5.0.sql
@@ -24,6 +24,7 @@ CREATE TABLE SENTRY_DB_PRIVILEGE
     PRIVILEGE_SCOPE VARCHAR(40),
     "SERVER_NAME" VARCHAR(4000),
     "TABLE_NAME" VARCHAR(4000),
+    "COLUMN_NAME" VARCHAR(4000),
     WITH_GRANT_OPTION CHAR(1) NOT NULL
 );
 
@@ -78,7 +79,7 @@ CREATE TABLE "SENTRY_VERSION" (
 ALTER TABLE SENTRY_VERSION ADD CONSTRAINT SENTRY_VERSION_PK PRIMARY KEY (VER_ID);
 
 -- Constraints for table SENTRY_DB_PRIVILEGE for class(es) [org.apache.sentry.provider.db.service.model.MSentryPrivilege]
-CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME",URI,"ACTION",WITH_GRANT_OPTION);
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME","COLUMN_NAME",URI,"ACTION",WITH_GRANT_OPTION);
 
 
 -- Constraints for table SENTRY_ROLE for class(es) [org.apache.sentry.provider.db.service.model.MSentryRole]

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.5.0.sql
index bc0d0d8..01cd60d 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-mysql-1.5.0.sql
@@ -31,6 +31,7 @@ CREATE TABLE `SENTRY_DB_PRIVILEGE` (
   `SERVER_NAME` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
   `DB_NAME` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
   `TABLE_NAME` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
+  `COLUMN_NAME` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
   `URI` VARCHAR(4000) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
   `ACTION` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
   `CREATE_TIME` BIGINT NOT NULL,
@@ -80,7 +81,7 @@ ALTER TABLE `SENTRY_VERSION`
   ADD CONSTRAINT `SENTRY_VERSION` PRIMARY KEY (`VER_ID`);
 
 ALTER TABLE `SENTRY_DB_PRIVILEGE`
-  ADD UNIQUE `SENTRY_DB_PRIV_PRIV_NAME_UNIQ` (`SERVER_NAME`,`DB_NAME`,`TABLE_NAME`,`URI`(250),`ACTION`,`WITH_GRANT_OPTION`);
+  ADD UNIQUE `SENTRY_DB_PRIV_PRIV_NAME_UNIQ` (`SERVER_NAME`,`DB_NAME`,`TABLE_NAME`,`COLUMN_NAME`,`URI`(250),`ACTION`,`WITH_GRANT_OPTION`);
 
 ALTER TABLE `SENTRY_DB_PRIVILEGE`
   ADD INDEX `SENTRY_PRIV_SERV_IDX` (`SERVER_NAME`);
@@ -92,6 +93,9 @@ ALTER TABLE `SENTRY_DB_PRIVILEGE`
   ADD INDEX `SENTRY_PRIV_TBL_IDX` (`TABLE_NAME`);
 
 ALTER TABLE `SENTRY_DB_PRIVILEGE`
+  ADD INDEX `SENTRY_PRIV_COL_IDX` (`COLUMN_NAME`);
+
+ALTER TABLE `SENTRY_DB_PRIVILEGE`
   ADD INDEX `SENTRY_PRIV_URI_IDX` (`URI`);
 
 ALTER TABLE `SENTRY_ROLE`

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.5.0.sql
index 5d8336c..5ff0081 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-oracle-1.5.0.sql
@@ -19,6 +19,7 @@ CREATE TABLE "SENTRY_DB_PRIVILEGE" (
   "SERVER_NAME" VARCHAR2(128) NOT NULL,
   "DB_NAME" VARCHAR2(128) NULL,
   "TABLE_NAME" VARCHAR2(128) NULL,
+  "COLUMN_NAME" VARCHAR2(128) NULL,
   "URI" VARCHAR2(4000) NULL,
   "ACTION" VARCHAR2(128) NOT NULL,
   "CREATE_TIME" NUMBER NOT NULL,
@@ -67,7 +68,7 @@ ALTER TABLE "SENTRY_GROUP"
 ALTER TABLE "SENTRY_VERSION" ADD CONSTRAINT "SENTRY_VERSION_PK" PRIMARY KEY ("VER_ID");
 
 ALTER TABLE "SENTRY_DB_PRIVILEGE"
-  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI","ACTION","WITH_GRANT_OPTION");
+  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","COLUMN_NAME","URI","ACTION","WITH_GRANT_OPTION");
 
 CREATE INDEX "SENTRY_SERV_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("SERVER_NAME");
 
@@ -75,6 +76,8 @@ CREATE INDEX "SENTRY_DB_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("DB_NAME");
 
 CREATE INDEX "SENTRY_TBL_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("TABLE_NAME");
 
+CREATE INDEX "SENTRY_COL_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("COLUMN_NAME");
+
 CREATE INDEX "SENTRY_URI_PRIV_IDX" ON "SENTRY_DB_PRIVILEGE" ("URI");
 
 ALTER TABLE "SENTRY_ROLE"

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.5.0.sql
index 24cac98..c451e33 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-postgres-1.5.0.sql
@@ -31,6 +31,7 @@ CREATE TABLE "SENTRY_DB_PRIVILEGE" (
   "SERVER_NAME" character varying(128) NOT NULL,
   "DB_NAME" character varying(128) DEFAULT NULL::character varying,
   "TABLE_NAME" character varying(128) DEFAULT NULL::character varying,
+  "COLUMN_NAME" character varying(128) DEFAULT NULL::character varying,
   "URI" character varying(4000) DEFAULT NULL::character varying,
   "ACTION" character varying(128) NOT NULL,
   "CREATE_TIME" BIGINT NOT NULL,
@@ -80,7 +81,7 @@ ALTER TABLE ONLY "SENTRY_GROUP"
 ALTER TABLE ONLY "SENTRY_VERSION" ADD CONSTRAINT "SENTRY_VERSION_PK" PRIMARY KEY ("VER_ID");
 
 ALTER TABLE ONLY "SENTRY_DB_PRIVILEGE"
-  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","URI", "ACTION","WITH_GRANT_OPTION");
+  ADD CONSTRAINT "SENTRY_DB_PRIV_PRIV_NAME_UNIQ" UNIQUE ("SERVER_NAME","DB_NAME","TABLE_NAME","COLUMN_NAME","URI", "ACTION","WITH_GRANT_OPTION");
 
 CREATE INDEX "SENTRY_PRIV_SERV_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("SERVER_NAME");
 
@@ -88,6 +89,8 @@ CREATE INDEX "SENTRY_PRIV_DB_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("DB_NAME
 
 CREATE INDEX "SENTRY_PRIV_TBL_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("TABLE_NAME");
 
+CREATE INDEX "SENTRY_PRIV_COL_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("COLUMN_NAME");
+
 CREATE INDEX "SENTRY_PRIV_URI_IDX" ON "SENTRY_DB_PRIVILEGE" USING btree ("URI");
 
 ALTER TABLE ONLY "SENTRY_ROLE"

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-db2-1.4.0-to-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-db2-1.4.0-to-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-db2-1.4.0-to-1.5.0.sql
index 866b22f..aa94ad1 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-db2-1.4.0-to-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-db2-1.4.0-to-1.5.0.sql
@@ -14,5 +14,10 @@ ALTER TABLE `SENTRY_GROUP` DROP `GRANTOR_PRINCIPAL`;
 ALTER TABLE `SENTRY_ROLE_DB_PRIVILEGE_MAP` ADD `GRANTOR_PRINCIPAL` VARCHAR(128);
 ALTER TABLE `SENTRY_ROLE_GROUP_MAP` ADD `GRANTOR_PRINCIPAL` VARCHAR(128);
 
+-- SENTRY-74
+ALTER TABLE `SENTRY_DB_PRIVILEGE` ADD `COLUMN_NAME` CHAR(4000);
+DROP INDEX SENTRYPRIVILEGENAME;
+CREATE UNIQUE INDEX SENTRYPRIVILEGENAME ON SENTRY_DB_PRIVILEGE ("SERVER_NAME",DB_NAME,"TABLE_NAME","COLUMN_NAME",URI,"ACTION",WITH_GRANT_OPTION);
+
 -- Version update
 UPDATE SENTRY_VERSION SET SCHEMA_VERSION='1.5.0', VERSION_COMMENT='Sentry release version 1.5.0' WHERE VER_ID=1;

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-derby-1.4.0-to-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-derby-1.4.0-to-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-derby-1.4.0-to-1.5.0.sql
index 027f45b..2499bd7 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-derby-1.4.0-to-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-derby-1.4.0-to-1.5.0.sql
@@ -1,5 +1,7 @@
 RUN '001-SENTRY-327.derby.sql';
 RUN '002-SENTRY-339.derby.sql';
 RUN '003-SENTRY-380.derby.sql';
+RUN '004-SENTRY-74.derby.sql';
+
 -- Version update
 UPDATE SENTRY_VERSION SET SCHEMA_VERSION='1.5.0', VERSION_COMMENT='Sentry release version 1.5.0' WHERE VER_ID=1;

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-mysql-1.4.0-to-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-mysql-1.4.0-to-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-mysql-1.4.0-to-1.5.0.sql
index 95247d1..3fc90f3 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-mysql-1.4.0-to-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-mysql-1.4.0-to-1.5.0.sql
@@ -2,6 +2,7 @@ SELECT 'Upgrading Sentry store schema from 1.4.0 to 1.5.0' AS ' ';
 SOURCE 001-SENTRY-327.mysql.sql;
 SOURCE 002-SENTRY-339.mysql.sql;
 SOURCE 003-SENTRY-380.mysql.sql;
+SOURCE 004-SENTRY-74.mysql.sql;
 
 UPDATE SENTRY_VERSION SET SCHEMA_VERSION='1.5.0', VERSION_COMMENT='Sentry release version 1.5.0' WHERE VER_ID=1;
 SELECT 'Finish upgrading Sentry store schema from 1.4.0 to 1.5.0' AS ' ';

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-oracle-1.4.0-to-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-oracle-1.4.0-to-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-oracle-1.4.0-to-1.5.0.sql
index 6195fc8..ded813d 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-oracle-1.4.0-to-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-oracle-1.4.0-to-1.5.0.sql
@@ -2,6 +2,7 @@ SELECT 'Upgrading Sentry store schema from 1.4.0 to 1.5.0' AS Status from dual;
 @001-SENTRY-327.oracle.sql;
 @002-SENTRY-339.oracle.sql;
 @003-SENTRY-380.oracle.sql;
+@004-SENTRY-74.oracle.sql;
 
 UPDATE SENTRY_VERSION SET SCHEMA_VERSION='1.5.0', VERSION_COMMENT='Sentry release version 1.5.0' WHERE VER_ID=1;
 SELECT 'Finished upgrading Sentry store schema from 1.4.0 to 1.5.0' AS Status from dual;

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-postgres-1.4.0-to-1.5.0.sql
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-postgres-1.4.0-to-1.5.0.sql b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-postgres-1.4.0-to-1.5.0.sql
index 0e48f21..2fbd2ca 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-postgres-1.4.0-to-1.5.0.sql
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry-upgrade-postgres-1.4.0-to-1.5.0.sql
@@ -2,6 +2,7 @@ SELECT 'Upgrading Sentry store schema from 1.4.0 to 1.5.0';
 \i 001-SENTRY-327.postgres.sql;
 \i 002-SENTRY-339.postgres.sql;
 \i 003-SENTRY-380.postgres.sql;
+\i 004-SENTRY-74.postgres.sql;
 
 UPDATE SENTRY_VERSION SET SCHEMA_VERSION='1.5.0', VERSION_COMMENT='Sentry release version 1.5.0' WHERE VER_ID=1;
 SELECT 'Finished upgrading Sentry store schema from 1.4.0 to 1.5.0';

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
index 7e6ade5..993ea46 100644
--- a/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
+++ b/sentry-provider/sentry-provider-db/src/main/resources/sentry_policy_service.thrift
@@ -49,6 +49,7 @@ struct TSentryPrivilege {
 7: required string action = "",
 8: optional i64 createTime, # Set on server side
 9: optional TSentryGrantOption grantOption = TSentryGrantOption.FALSE
+10: optional string columnName = "",
 }
 
 # TODO can this be deleted? it's not adding value to TAlterSentryRoleAddGroupsRequest
@@ -104,11 +105,13 @@ struct TAlterSentryRoleGrantPrivilegeRequest {
 1: required i32 protocol_version = sentry_common_service.TSENTRY_SERVICE_V1,
 2: required string requestorUserName, # user on whose behalf the request is issued
 3: required string roleName,
-5: required TSentryPrivilege privilege
+5: optional TSentryPrivilege privilege,
+6: optional set<TSentryPrivilege> privileges
 }
 struct TAlterSentryRoleGrantPrivilegeResponse {
 1: required sentry_common_service.TSentryResponseStatus status
 2: optional TSentryPrivilege privilege
+3: optional set<TSentryPrivilege> privileges
 }
 
 # REVOKE ... ON ... FROM ROLE ...
@@ -116,7 +119,8 @@ struct TAlterSentryRoleRevokePrivilegeRequest {
 1: required i32 protocol_version = sentry_common_service.TSENTRY_SERVICE_V1,
 2: required string requestorUserName, # user on whose behalf the request is issued
 3: required string roleName,
-5: required TSentryPrivilege privilege
+5: optional TSentryPrivilege privilege,
+6: optional set<TSentryPrivilege> privileges
 }
 struct TAlterSentryRoleRevokePrivilegeResponse {
 1: required sentry_common_service.TSentryResponseStatus status
@@ -144,6 +148,7 @@ struct TSentryAuthorizable {
 2: optional string uri,
 3: optional string db,
 4: optional string table,
+5: optional string column,
 }
 
 # SHOW GRANT

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestAuditMetadataLogEntity.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestAuditMetadataLogEntity.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestAuditMetadataLogEntity.java
index cd0a435..95b51e9 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestAuditMetadataLogEntity.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestAuditMetadataLogEntity.java
@@ -31,7 +31,7 @@ public class TestAuditMetadataLogEntity extends TestCase {
   public void testToJsonFormatLog() throws Throwable {
     AuditMetadataLogEntity amle = new AuditMetadataLogEntity("serviceName",
         "userName", "impersonator", "ipAddress", "operation", "eventTime",
-        "operationText", "allowed", "databaseName", "tableName",
+        "operationText", "allowed", "databaseName", "tableName", "columnName",
         "resourcePath", "objectType");
     String jsonAuditLog = amle.toJsonFormatLog();
     ContainerNode rootNode = AuditMetadataLogEntity.parse(jsonAuditLog);
@@ -48,6 +48,7 @@ public class TestAuditMetadataLogEntity extends TestCase {
     assertEntryEquals(rootNode, Constants.LOG_FIELD_DATABASE_NAME,
         "databaseName");
     assertEntryEquals(rootNode, Constants.LOG_FIELD_TABLE_NAME, "tableName");
+    assertEntryEquals(rootNode, Constants.LOG_FIELD_COLUMN_NAME, "columnName");
     assertEntryEquals(rootNode, Constants.LOG_FIELD_RESOURCE_PATH,
         "resourcePath");
     assertEntryEquals(rootNode, Constants.LOG_FIELD_OBJECT_TYPE, "objectType");

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/13e83d6e/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestJsonLogEntityFactory.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestJsonLogEntityFactory.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestJsonLogEntityFactory.java
index fc9c716..bce4717 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestJsonLogEntityFactory.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/log/entity/TestJsonLogEntityFactory.java
@@ -20,6 +20,7 @@ package org.apache.sentry.provider.db.log.entity;
 
 import static junit.framework.Assert.assertEquals;
 
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -28,6 +29,7 @@ import org.apache.log4j.Logger;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.provider.db.log.util.CommandUtil;
 import org.apache.sentry.provider.db.log.util.Constants;
+import org.apache.sentry.provider.db.service.model.MSentryPrivilege;
 import org.apache.sentry.provider.db.service.thrift.TAlterSentryRoleAddGroupsRequest;
 import org.apache.sentry.provider.db.service.thrift.TAlterSentryRoleAddGroupsResponse;
 import org.apache.sentry.provider.db.service.thrift.TAlterSentryRoleDeleteGroupsRequest;
@@ -48,6 +50,8 @@ import org.apache.sentry.service.thrift.Status;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.google.common.collect.Sets;
+
 public class TestJsonLogEntityFactory {
 
   private static Configuration conf;
@@ -122,10 +126,18 @@ public class TestJsonLogEntityFactory {
 
     TSentryPrivilege privilege = getPrivilege(AccessConstants.ALL,
         PrivilegeScope.DATABASE.name(), TEST_DATABASE_NAME, null, null, null);
-    request.setPrivilege(privilege);
+    Set<TSentryPrivilege> privileges = Sets.newHashSet();
+    privileges.add(privilege);
+    request.setPrivileges(privileges);
     response.setStatus(Status.OK());
-    AuditMetadataLogEntity amle = (AuditMetadataLogEntity) JsonLogEntityFactory
-        .getInstance().createJsonLogEntity(request, response, conf);
+    AuditMetadataLogEntity amle = new AuditMetadataLogEntity();
+    Set<JsonLogEntity> amles =  JsonLogEntityFactory
+        .getInstance().createJsonLogEntitys(request, response, conf);
+    assertEquals(amles.size(),1);
+    for (JsonLogEntity amle1 : amles) {
+      amle = (AuditMetadataLogEntity) amle1;
+      break;
+    }
     assertCommon(amle, Constants.TRUE, Constants.OPERATION_GRANT_PRIVILEGE,
         "GRANT ALL ON DATABASE testDB TO ROLE testRole", TEST_DATABASE_NAME,
         null, null, Constants.OBJECT_TYPE_PRINCIPAL);
@@ -133,10 +145,17 @@ public class TestJsonLogEntityFactory {
 
     privilege = getPrivilege(AccessConstants.ALL, PrivilegeScope.TABLE.name(),
         null, TEST_TABLE_NAME, null, null);
-    request.setPrivilege(privilege);
+    privileges = Sets.newHashSet();
+    privileges.add(privilege);
+    request.setPrivileges(privileges);
     response.setStatus(Status.InvalidInput("", null));
-    amle = (AuditMetadataLogEntity) JsonLogEntityFactory.getInstance()
-        .createJsonLogEntity(request, response, conf);
+    amles =  JsonLogEntityFactory.getInstance()
+        .createJsonLogEntitys(request, response, conf);
+    assertEquals(amles.size(),1);
+    for (JsonLogEntity amle1 : amles) {
+      amle = (AuditMetadataLogEntity) amle1;
+      break;
+    }
     assertCommon(amle, Constants.FALSE, Constants.OPERATION_GRANT_PRIVILEGE,
         "GRANT ALL ON TABLE testTable TO ROLE testRole", null, TEST_TABLE_NAME,
         null, Constants.OBJECT_TYPE_PRINCIPAL);
@@ -152,10 +171,18 @@ public class TestJsonLogEntityFactory {
 
     TSentryPrivilege privilege = getPrivilege(AccessConstants.ALL,
         PrivilegeScope.DATABASE.name(), TEST_DATABASE_NAME, null, null, null);
-    request.setPrivilege(privilege);
+    Set<TSentryPrivilege> privileges = Sets.newHashSet();
+    privileges.add(privilege);
+    request.setPrivileges(privileges);
     response.setStatus(Status.OK());
-    AuditMetadataLogEntity amle = (AuditMetadataLogEntity) JsonLogEntityFactory
-        .getInstance().createJsonLogEntity(request, response, conf);
+    AuditMetadataLogEntity amle = new AuditMetadataLogEntity();
+    Set<JsonLogEntity> amles =  JsonLogEntityFactory
+        .getInstance().createJsonLogEntitys(request, response, conf);
+    assertEquals(amles.size(),1);
+    for (JsonLogEntity amle1 : amles) {
+      amle = (AuditMetadataLogEntity) amle1;
+      break;
+    }
     assertCommon(amle, Constants.TRUE, Constants.OPERATION_REVOKE_PRIVILEGE,
         "REVOKE ALL ON DATABASE testDB FROM ROLE testRole", TEST_DATABASE_NAME,
         null, null, Constants.OBJECT_TYPE_PRINCIPAL);
@@ -163,10 +190,17 @@ public class TestJsonLogEntityFactory {
 
     privilege = getPrivilege(AccessConstants.ALL, PrivilegeScope.TABLE.name(),
         null, TEST_TABLE_NAME, null, null);
-    request.setPrivilege(privilege);
+    privileges = Sets.newHashSet();
+    privileges.add(privilege);
+    request.setPrivileges(privileges);
     response.setStatus(Status.InvalidInput("", null));
-    amle = (AuditMetadataLogEntity) JsonLogEntityFactory.getInstance()
-        .createJsonLogEntity(request, response, conf);
+    amles =  JsonLogEntityFactory.getInstance()
+        .createJsonLogEntitys(request, response, conf);
+    assertEquals(amles.size(),1);
+    for (JsonLogEntity amle1 : amles) {
+      amle = (AuditMetadataLogEntity) amle1;
+      break;
+    }
     assertCommon(amle, Constants.FALSE, Constants.OPERATION_REVOKE_PRIVILEGE,
         "REVOKE ALL ON TABLE testTable FROM ROLE testRole", null,
         TEST_TABLE_NAME, null, Constants.OBJECT_TYPE_PRINCIPAL);